summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp8
-rw-r--r--apct-tests/perftests/core/src/android/opengl/perftests/MatrixPerfTest.java397
-rw-r--r--apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java12
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java48
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobStore.java51
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java86
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java24
-rw-r--r--apex/jobscheduler/service/java/com/android/server/tare/Ledger.java2
-rw-r--r--core/api/current.txt29
-rw-r--r--core/api/system-current.txt10
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ActivityManager.java34
-rw-r--r--core/java/android/app/ActivityManagerInternal.java2
-rw-r--r--core/java/android/app/ActivityThread.java3
-rw-r--r--core/java/android/app/IActivityManager.aidl6
-rw-r--r--core/java/android/app/Notification.java4
-rw-r--r--core/java/android/companion/virtual/VirtualDeviceManager.java4
-rw-r--r--core/java/android/companion/virtual/audio/VirtualAudioDevice.java20
-rw-r--r--core/java/android/content/Context.java7
-rw-r--r--core/java/android/content/pm/PackageInstaller.aidl1
-rw-r--r--core/java/android/content/pm/PackageInstaller.java278
-rw-r--r--core/java/android/content/pm/PackageManager.java7
-rw-r--r--core/java/android/content/pm/PermissionMethod.java36
-rw-r--r--core/java/android/content/res/ResourceTimer.java42
-rw-r--r--core/java/android/hardware/DataSpace.java49
-rw-r--r--core/java/android/hardware/camera2/params/OutputConfiguration.java8
-rw-r--r--core/java/android/hardware/display/DisplayManager.java59
-rw-r--r--core/java/android/hardware/hdmi/HdmiControlManager.java69
-rw-r--r--core/java/android/hardware/lights/Light.java24
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java32
-rw-r--r--core/java/android/os/FileUtils.java81
-rw-r--r--core/java/android/os/Process.java19
-rw-r--r--core/java/android/service/credentials/Action.java98
-rw-r--r--core/java/android/service/credentials/CreateCredentialCallback.java64
-rw-r--r--core/java/android/service/credentials/CreateCredentialRequest.aidl3
-rw-r--r--core/java/android/service/credentials/CreateCredentialRequest.java103
-rw-r--r--core/java/android/service/credentials/CreateCredentialResponse.aidl19
-rw-r--r--core/java/android/service/credentials/CreateCredentialResponse.java140
-rw-r--r--core/java/android/service/credentials/Credential.java100
-rw-r--r--core/java/android/service/credentials/CredentialEntry.java216
-rw-r--r--core/java/android/service/credentials/CredentialProviderService.java119
-rw-r--r--core/java/android/service/credentials/CredentialsDisplayContent.java188
-rw-r--r--core/java/android/service/credentials/GetCredentialOption.java96
-rw-r--r--core/java/android/service/credentials/GetCredentialsCallback.java65
-rw-r--r--core/java/android/service/credentials/GetCredentialsRequest.aidl3
-rw-r--r--core/java/android/service/credentials/GetCredentialsRequest.java156
-rw-r--r--core/java/android/service/credentials/GetCredentialsResponse.aidl3
-rw-r--r--core/java/android/service/credentials/GetCredentialsResponse.java128
-rw-r--r--core/java/android/service/credentials/ICreateCredentialCallback.aidl13
-rw-r--r--core/java/android/service/credentials/ICredentialProviderService.aidl33
-rw-r--r--core/java/android/service/credentials/IGetCredentialsCallback.aidl13
-rw-r--r--core/java/android/service/credentials/SaveEntry.java162
-rw-r--r--core/java/android/service/voice/VoiceInteractionService.java28
-rw-r--r--core/java/android/view/IWindowManager.aidl8
-rw-r--r--core/java/android/view/SurfaceControl.java5
-rw-r--r--core/java/android/view/ThreadedRenderer.java8
-rw-r--r--core/java/android/view/ViewRootImpl.java3
-rw-r--r--core/java/android/view/WindowManagerGlobal.java61
-rw-r--r--core/java/android/view/accessibility/AccessibilityEvent.java32
-rw-r--r--core/java/android/view/contentcapture/MainContentCaptureSession.java7
-rw-r--r--core/java/android/view/translation/UiTranslationController.java5
-rw-r--r--core/java/android/widget/TextView.java8
-rw-r--r--core/java/android/window/ScreenCapture.aidl26
-rw-r--r--core/java/android/window/ScreenCapture.java335
-rw-r--r--core/java/com/android/internal/jank/EventLogTags.logtags10
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java3
-rwxr-xr-xcore/java/com/android/internal/util/function/pooled/PooledLambda.java501
-rw-r--r--core/jni/android_media_AudioProductStrategies.cpp14
-rw-r--r--core/jni/android_util_AssetManager.cpp4
-rw-r--r--core/jni/android_window_ScreenCapture.cpp105
-rw-r--r--core/res/Android.bp4
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--core/res/res/values/strings.xml2
-rw-r--r--core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java16
-rw-r--r--core/tests/coretests/src/android/os/FileUtilsTest.java50
-rw-r--r--core/tests/coretests/src/android/os/ProcessTest.java3
-rw-r--r--graphics/java/android/graphics/ColorSpace.java6
-rw-r--r--graphics/java/android/graphics/HardwareRenderer.java102
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java32
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java20
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt110
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt23
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt11
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java10
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java47
-rw-r--r--libs/hwui/Android.bp1
-rw-r--r--libs/hwui/DeviceInfo.cpp6
-rw-r--r--libs/hwui/HardwareBitmapUploader.cpp6
-rw-r--r--libs/hwui/MemoryPolicy.cpp69
-rw-r--r--libs/hwui/MemoryPolicy.h62
-rw-r--r--libs/hwui/Properties.cpp4
-rw-r--r--libs/hwui/Properties.h13
-rw-r--r--libs/hwui/jni/android_graphics_HardwareRenderer.cpp13
-rw-r--r--libs/hwui/renderthread/CacheManager.cpp183
-rw-r--r--libs/hwui/renderthread/CacheManager.h46
-rw-r--r--libs/hwui/renderthread/CanvasContext.cpp36
-rw-r--r--libs/hwui/renderthread/CanvasContext.h4
-rw-r--r--libs/hwui/renderthread/DrawFrameTask.cpp4
-rw-r--r--libs/hwui/renderthread/RenderProxy.cpp5
-rw-r--r--libs/hwui/renderthread/RenderThread.cpp9
-rw-r--r--libs/hwui/renderthread/RenderThread.h5
-rw-r--r--libs/hwui/tests/unit/CacheManagerTests.cpp6
-rw-r--r--media/java/android/media/MediaFormat.java65
-rw-r--r--media/java/android/media/projection/MediaProjectionGlobal.java120
-rw-r--r--media/native/midi/Android.bp4
-rw-r--r--media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java2
-rw-r--r--native/android/Android.bp3
-rw-r--r--packages/SettingsLib/Spa/TEST_MAPPING3
-rw-r--r--packages/SettingsLib/Spa/build.gradle4
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt6
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt5
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt (renamed from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt)13
-rw-r--r--packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt2
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt8
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt16
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt14
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt31
-rw-r--r--packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt22
-rw-r--r--packages/SettingsLib/SpaPrivileged/Android.bp6
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt (renamed from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt)43
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt18
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt (renamed from packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt)16
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt7
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt13
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt19
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt22
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt6
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/Android.bp46
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml28
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt113
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java23
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java76
-rw-r--r--packages/SystemUI/TEST_MAPPING19
-rw-r--r--packages/SystemUI/res/values/ids.xml3
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt4
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt48
-rw-r--r--packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractor.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt68
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt177
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java53
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt46
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt17
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt112
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositoryImplTest.kt171
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/domain/interactor/WifiInteractorTest.kt198
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt201
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt668
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java5
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java9
-rw-r--r--services/companion/java/com/android/server/companion/virtual/InputController.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java163
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerShellCommand.java17
-rw-r--r--services/core/java/com/android/server/am/BroadcastConstants.java10
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java25
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java9
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java32
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java77
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java29
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessProfileRecord.java13
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java7
-rw-r--r--services/core/java/com/android/server/am/ProcessStateRecord.java13
-rw-r--r--services/core/java/com/android/server/appop/AppOpsService.java31
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTracker.java5
-rw-r--r--services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java114
-rw-r--r--services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java9
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java2
-rw-r--r--services/core/java/com/android/server/display/TEST_MAPPING9
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java2
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java16
-rw-r--r--services/core/java/com/android/server/input/InputManagerInternal.java (renamed from core/java/android/hardware/input/InputManagerInternal.java)2
-rw-r--r--services/core/java/com/android/server/input/InputManagerService.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/HandwritingModeController.java2
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java2
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssLocationProvider.java7
-rw-r--r--services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java4
-rw-r--r--services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java5
-rw-r--r--services/core/java/com/android/server/media/MediaRouterService.java40
-rw-r--r--services/core/java/com/android/server/pm/DexOptHelper.java12
-rw-r--r--services/core/java/com/android/server/pm/InstallSource.java3
-rw-r--r--services/core/java/com/android/server/pm/PackageDexOptimizer.java5
-rw-r--r--services/core/java/com/android/server/policy/DeviceStateProviderImpl.java2
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java2
-rw-r--r--services/core/java/com/android/server/power/Notifier.java2
-rw-r--r--services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java7
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java4
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java51
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java14
-rw-r--r--services/core/java/com/android/server/wm/ContentRecorder.java15
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java29
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java5
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java30
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/RunningTasks.java16
-rw-r--r--services/core/java/com/android/server/wm/Session.java4
-rw-r--r--services/core/java/com/android/server/wm/Task.java80
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java2
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java5
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java78
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java20
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java18
-rw-r--r--services/core/jni/com_android_server_input_InputManagerService.cpp36
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java29
-rw-r--r--services/incremental/IncrementalService.cpp4
-rw-r--r--services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java165
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java234
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java173
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java45
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java79
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java4
-rw-r--r--services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java8
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/TEST_MAPPING16
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/BrightnessEventTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt7
-rw-r--r--services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java2
-rw-r--r--services/tests/wmtests/AndroidManifest.xml5
-rw-r--r--services/tests/wmtests/res/values/styles.xml24
-rw-r--r--services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java8
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java176
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java5
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java41
-rw-r--r--services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java62
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java44
-rw-r--r--telephony/java/android/telephony/NetworkRegistrationInfo.java25
-rw-r--r--tests/Codegen/Android.bp8
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt15
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt8
-rw-r--r--tests/HandwritingIme/OWNERS3
-rw-r--r--tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java70
-rw-r--r--tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java15
-rw-r--r--tests/RollbackTest/Android.bp1
-rw-r--r--tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java912
-rw-r--r--tools/aapt2/Android.bp4
-rw-r--r--tools/aapt2/ResourceMetadata.proto49
-rw-r--r--tools/aapt2/cmd/Optimize.cpp14
-rw-r--r--tools/aapt2/optimize/Obfuscator.cpp (renamed from tools/aapt2/optimize/ResourcePathShortener.cpp)33
-rw-r--r--tools/aapt2/optimize/Obfuscator.h (renamed from tools/aapt2/optimize/ResourcePathShortener.h)16
-rw-r--r--tools/aapt2/optimize/Obfuscator_test.cpp (renamed from tools/aapt2/optimize/ResourcePathShortener_test.cpp)47
-rw-r--r--tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt12
-rw-r--r--tools/processors/staledataclass/Android.bp18
-rw-r--r--tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt1
320 files changed, 9296 insertions, 3174 deletions
diff --git a/Android.bp b/Android.bp
index 2c550fdfb675..2f5c851f6d80 100644
--- a/Android.bp
+++ b/Android.bp
@@ -342,6 +342,14 @@ java_defaults {
"staledataclass-annotation-processor",
"error_prone_android_framework",
],
+ // Exports needed for staledataclass-annotation-processor, see b/139342589.
+ javacflags: [
+ "-J--add-modules=jdk.compiler",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ ],
required: [
// TODO(b/120066492): remove default_television.xml when the build system
// propagates "required" properly.
diff --git a/apct-tests/perftests/core/src/android/opengl/perftests/MatrixPerfTest.java b/apct-tests/perftests/core/src/android/opengl/perftests/MatrixPerfTest.java
new file mode 100644
index 000000000000..b29614817ee2
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/opengl/perftests/MatrixPerfTest.java
@@ -0,0 +1,397 @@
+/*
+ * 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.opengl.perftests;
+
+import android.opengl.Matrix;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.filters.LargeTest;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Random;
+
+@LargeTest
+public class MatrixPerfTest {
+ @Rule
+ public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+ @Rule
+ public float[] array = new float[48];
+
+ @Rule
+ public float[] bigArray = new float[16 * 1024 * 1024];
+
+
+ @Test
+ public void testMultiplyMM() {
+ Random rng = new Random();
+ for (int i = 0; i < 32; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMM(array, 32, array, 16, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMMLeftOverlapResult() {
+ Random rng = new Random();
+ for (int i = 0; i < 32; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMM(array, 16, array, 16, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMMRightOverlapResult() {
+ Random rng = new Random();
+ for (int i = 0; i < 32; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMM(array, 0, array, 16, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMMAllOverlap() {
+ Random rng = new Random();
+ for (int i = 0; i < 16; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMM(array, 0, array, 0, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMMOutputBigArray() {
+ Random rng = new Random();
+ for (int i = 0; i < 32; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMM(bigArray, 1024, array, 16, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMMAllBigArray() {
+ Random rng = new Random();
+ for (int i = 0; i < 32; i++) {
+ bigArray[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMM(bigArray, 1024, bigArray, 16, bigArray, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMV() {
+ Random rng = new Random();
+ for (int i = 0; i < 20; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMV(array, 20, array, 4, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMVLeftOverlapResult() {
+ Random rng = new Random();
+ for (int i = 0; i < 20; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMV(array, 4, array, 4, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMVRightOverlapResult() {
+ Random rng = new Random();
+ for (int i = 0; i < 32; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMV(array, 0, array, 16, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMVAllOverlap() {
+ Random rng = new Random();
+ for (int i = 0; i < 16; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMV(array, 0, array, 0, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMVOutputBigArray() {
+ Random rng = new Random();
+ for (int i = 0; i < 20; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMV(bigArray, 1024, array, 16, array, 0);
+ }
+ }
+
+ @Test
+ public void testMultiplyMVAllBigArray() {
+ Random rng = new Random();
+ for (int i = 0; i < 20; i++) {
+ bigArray[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.multiplyMV(bigArray, 1024, bigArray, 16, bigArray, 0);
+ }
+ }
+
+ @Test
+ public void testTransposeM() {
+ Random rng = new Random();
+ for (int i = 0; i < 16; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.transposeM(array, 16, array, 0);
+ }
+ }
+
+ @Test
+ public void testInvertM() {
+ // non-singular matrix
+ array[ 0] = 0.814f;
+ array[ 1] = 4.976f;
+ array[ 2] = -3.858f;
+ array[ 3] = 7.206f;
+ array[ 4] = 5.112f;
+ array[ 5] = -2.420f;
+ array[ 6] = 8.791f;
+ array[ 7] = 6.426f;
+ array[ 8] = 2.945f;
+ array[ 9] = 1.801f;
+ array[10] = -2.594f;
+ array[11] = 2.663f;
+ array[12] = -5.003f;
+ array[13] = -4.188f;
+ array[14] = 3.340f;
+ array[15] = -1.235f;
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.invertM(array, 16, array, 0);
+ }
+ }
+
+ @Test
+ public void testOrthoM() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.orthoM(array, 0, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
+ }
+ }
+
+ @Test
+ public void testFrustumM() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.frustumM(array, 0, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f);
+ }
+ }
+
+ @Test
+ public void testPerspectiveM() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.perspectiveM(array, 0, 45.0f, 1.0f, 1.0f, 100.0f);
+ }
+ }
+
+ @Test
+ public void testLength() {
+ Random rng = new Random();
+ for (int i = 0; i < 3; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.length(array[0], array[1], array[2]);
+ }
+ }
+
+ @Test
+ public void testSetIdentityM() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.setIdentityM(array, 0);
+ }
+ }
+
+ @Test
+ public void testScaleM() {
+ Random rng = new Random();
+ for (int i = 0; i < 19; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.scaleM(array, 19, array, 0, array[16], array[17], array[18]);
+ }
+ }
+
+ @Test
+ public void testScaleMInPlace() {
+ Random rng = new Random();
+ for (int i = 0; i < 19; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.scaleM(array, 0, array[16], array[17], array[18]);
+ }
+ }
+
+ @Test
+ public void testTranslateM() {
+ Random rng = new Random();
+ for (int i = 0; i < 19; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.translateM(array, 19, array, 0, array[16], array[17], array[18]);
+ }
+ }
+
+ @Test
+ public void testTranslateMInPlace() {
+ Random rng = new Random();
+ for (int i = 0; i < 19; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.translateM(array, 0, array[16], array[17], array[18]);
+ }
+ }
+
+ @Test
+ public void testRotateM() {
+ Random rng = new Random();
+ for (int i = 0; i < 20; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.rotateM(array, 20, array, 0, array[16], array[17], array[18], array[19]);
+ }
+ }
+
+ @Test
+ public void testRotateMInPlace() {
+ Random rng = new Random();
+ for (int i = 0; i < 20; i++) {
+ array[i] = rng.nextFloat();
+ }
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.rotateM(array, 0, array[16], array[17], array[18], array[19]);
+ }
+ }
+
+ @Test
+ public void testSetRotateM() {
+ Random rng = new Random();
+ array[0] = rng.nextFloat() * 90.0f;
+ array[1] = rng.nextFloat() + 0.5f;
+ array[2] = rng.nextFloat() + 0.5f;
+ array[3] = rng.nextFloat() + 0.5f;
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.setRotateM(array, 4, array[0], array[1], array[2], array[3]);
+ }
+ }
+
+ @Test
+ public void testSetRotateEulerM() {
+ Random rng = new Random();
+ array[0] = rng.nextFloat() * 90.0f;
+ array[1] = rng.nextFloat() * 90.0f;
+ array[2] = rng.nextFloat() * 90.0f;
+
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.setRotateEulerM(array, 3, array[0], array[1], array[2]);
+ }
+ }
+
+ @Test
+ public void testSetLookAtM() {
+ final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ while (state.keepRunning()) {
+ Matrix.setLookAtM(array, 9,
+ 1.0f, 0.0f, 0.0f,
+ 1.0f, 0.0f, 1.0f,
+ 0.0f, 1.0f, 0.0f);
+ }
+ }
+}
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index be0e02595b60..6af24be3a372 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -227,6 +227,9 @@ public class EconomyManager {
public static final String KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP =
"js_min_satiated_balance_other_app";
/** @hide */
+ public static final String KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER =
+ "js_min_satiated_balance_increment_updater";
+ /** @hide */
public static final String KEY_JS_MAX_SATIATED_BALANCE =
"js_max_satiated_balance";
/** @hide */
@@ -509,6 +512,15 @@ public class EconomyManager {
public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_ONGOING_CAKES = arcToCake(0);
/** @hide */
public static final long DEFAULT_JS_REWARD_OTHER_USER_INTERACTION_MAX_CAKES = arcToCake(5000);
+ /**
+ * How many credits to increase the updating app's min satiated balance by for each app that it
+ * is responsible for updating.
+ * @hide
+ */
+ public static final long DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES =
+ // Research indicates that the median time between popular app updates is 13-14 days,
+ // so adjust by 14 to amortize over that time.
+ DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES / 14;
/** @hide */
public static final long DEFAULT_JS_ACTION_JOB_MAX_START_CTP_CAKES = arcToCake(3);
/** @hide */
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 1e13dbf9b057..1775d908e21b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -882,6 +882,8 @@ public class JobSchedulerService extends com.android.server.SystemService
// a user-initiated action, it should be fine to just
// put USER instead of UNINSTALL or DISABLED.
cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
+ /* includeSchedulingApp */ true,
+ /* includeSourceApp */ true,
JobParameters.STOP_REASON_USER,
JobParameters.INTERNAL_STOP_REASON_UNINSTALL,
"app disabled");
@@ -932,6 +934,7 @@ public class JobSchedulerService extends com.android.server.SystemService
// get here, but since this is generally a user-initiated action, it should
// be fine to just put USER instead of UNINSTALL or DISABLED.
cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
+ /* includeSchedulingApp */ true, /* includeSourceApp */ true,
JobParameters.STOP_REASON_USER,
JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "app uninstalled");
for (int c = 0; c < mControllers.size(); ++c) {
@@ -986,7 +989,12 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "Removing jobs for pkg " + pkgName + " at uid " + pkgUid);
}
synchronized (mLock) {
+ // Exclude jobs scheduled on behalf of this app for now because SyncManager
+ // and other job proxy agents may not know to reschedule the job properly
+ // after force stop.
+ // TODO(209852664): determine how to best handle syncs & other proxied jobs
cancelJobsForPackageAndUidLocked(pkgName, pkgUid,
+ /* includeSchedulingApp */ true, /* includeSourceApp */ false,
JobParameters.STOP_REASON_USER,
JobParameters.INTERNAL_STOP_REASON_CANCELED,
"app force stopped");
@@ -1304,16 +1312,18 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ private final Consumer<JobStatus> mCancelJobDueToUserRemovalConsumer = (toRemove) -> {
+ // There's no guarantee that the process has been stopped by the time we get
+ // here, but since this is a user-initiated action, it should be fine to just
+ // put USER instead of UNINSTALL or DISABLED.
+ cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
+ JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "user removed");
+ };
+
private void cancelJobsForUserLocked(int userHandle) {
- final List<JobStatus> jobsForUser = mJobs.getJobsByUser(userHandle);
- for (int i = 0; i < jobsForUser.size(); i++) {
- JobStatus toRemove = jobsForUser.get(i);
- // There's no guarantee that the process has been stopped by the time we get here,
- // but since this is a user-initiated action, it should be fine to just put USER
- // instead of UNINSTALL or DISABLED.
- cancelJobImplLocked(toRemove, null, JobParameters.STOP_REASON_USER,
- JobParameters.INTERNAL_STOP_REASON_UNINSTALL, "user removed");
- }
+ mJobs.forEachJob(
+ (job) -> job.getUserId() == userHandle || job.getSourceUserId() == userHandle,
+ mCancelJobDueToUserRemovalConsumer);
}
private void cancelJobsForNonExistentUsers() {
@@ -1324,15 +1334,31 @@ public class JobSchedulerService extends com.android.server.SystemService
}
private void cancelJobsForPackageAndUidLocked(String pkgName, int uid,
+ boolean includeSchedulingApp, boolean includeSourceApp,
@JobParameters.StopReason int reason, int internalReasonCode, String debugReason) {
+ if (!includeSchedulingApp && !includeSourceApp) {
+ Slog.wtfStack(TAG,
+ "Didn't indicate whether to cancel jobs for scheduling and/or source app");
+ includeSourceApp = true;
+ }
if ("android".equals(pkgName)) {
Slog.wtfStack(TAG, "Can't cancel all jobs for system package");
return;
}
- final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
+ final List<JobStatus> jobsForUid = new ArrayList<>();
+ if (includeSchedulingApp) {
+ mJobs.getJobsByUid(uid, jobsForUid);
+ }
+ if (includeSourceApp) {
+ mJobs.getJobsBySourceUid(uid, jobsForUid);
+ }
for (int i = jobsForUid.size() - 1; i >= 0; i--) {
final JobStatus job = jobsForUid.get(i);
- if (job.getSourcePackageName().equals(pkgName)) {
+ final boolean shouldCancel =
+ (includeSchedulingApp
+ && job.getServiceComponent().getPackageName().equals(pkgName))
+ || (includeSourceApp && job.getSourcePackageName().equals(pkgName));
+ if (shouldCancel) {
cancelJobImplLocked(job, null, reason, internalReasonCode, debugReason);
}
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index fcfb45c5f1df..78ab06c9e332 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -22,6 +22,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_TEST;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import static com.android.server.job.JobSchedulerService.sSystemClock;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.content.ComponentName;
@@ -32,7 +33,6 @@ import android.os.Handler;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.SystemClock;
-import android.os.UserHandle;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.ArraySet;
@@ -287,26 +287,37 @@ public final class JobStore {
}
/**
- * @param userHandle User for whom we are querying the list of jobs.
- * @return A list of all the jobs scheduled for the provided user. Never null.
+ * @param sourceUid Uid of the source app.
+ * @return A list of all the jobs scheduled for the source app. Never null.
*/
- public List<JobStatus> getJobsByUser(int userHandle) {
- return mJobSet.getJobsByUser(userHandle);
+ @NonNull
+ public List<JobStatus> getJobsBySourceUid(int sourceUid) {
+ return mJobSet.getJobsBySourceUid(sourceUid);
+ }
+
+ public void getJobsBySourceUid(int sourceUid, @NonNull List<JobStatus> insertInto) {
+ mJobSet.getJobsBySourceUid(sourceUid, insertInto);
}
/**
* @param uid Uid of the requesting app.
* @return All JobStatus objects for a given uid from the master list. Never null.
*/
+ @NonNull
public List<JobStatus> getJobsByUid(int uid) {
return mJobSet.getJobsByUid(uid);
}
+ public void getJobsByUid(int uid, @NonNull List<JobStatus> insertInto) {
+ mJobSet.getJobsByUid(uid, insertInto);
+ }
+
/**
* @param uid Uid of the requesting app.
* @param jobId Job id, specified at schedule-time.
* @return the JobStatus that matches the provided uId and jobId, or null if none found.
*/
+ @Nullable
public JobStatus getJobByUidAndJobId(int uid, int jobId) {
return mJobSet.get(uid, jobId);
}
@@ -1220,27 +1231,31 @@ public final class JobStore {
public List<JobStatus> getJobsByUid(int uid) {
ArrayList<JobStatus> matchingJobs = new ArrayList<JobStatus>();
+ getJobsByUid(uid, matchingJobs);
+ return matchingJobs;
+ }
+
+ public void getJobsByUid(int uid, List<JobStatus> insertInto) {
ArraySet<JobStatus> jobs = mJobs.get(uid);
if (jobs != null) {
- matchingJobs.addAll(jobs);
+ insertInto.addAll(jobs);
}
- return matchingJobs;
}
- // By user, not by uid, so we need to traverse by key and check
- public List<JobStatus> getJobsByUser(int userId) {
+ @NonNull
+ public List<JobStatus> getJobsBySourceUid(int sourceUid) {
final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
- for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
- if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
- final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
- if (jobs != null) {
- result.addAll(jobs);
- }
- }
- }
+ getJobsBySourceUid(sourceUid, result);
return result;
}
+ public void getJobsBySourceUid(int sourceUid, List<JobStatus> insertInto) {
+ final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
+ if (jobs != null) {
+ insertInto.addAll(jobs);
+ }
+ }
+
public boolean add(JobStatus job) {
final int uid = job.getUid();
final int sourceUid = job.getSourceUid();
@@ -1382,7 +1397,7 @@ public final class JobStore {
}
public void forEachJob(@Nullable Predicate<JobStatus> filterPredicate,
- Consumer<JobStatus> functor) {
+ @NonNull Consumer<JobStatus> functor) {
for (int uidIndex = mJobs.size() - 1; uidIndex >= 0; uidIndex--) {
ArraySet<JobStatus> jobs = mJobs.valueAt(uidIndex);
if (jobs != null) {
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 c13e1dd978d6..4a26d213a48a 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -81,6 +81,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;
/**
@@ -164,6 +165,10 @@ public class InternalResourceService extends SystemService {
@GuardedBy("mLock")
private final SparseArrayMap<String, Boolean> mVipOverrides = new SparseArrayMap<>();
+ /** Set of apps each installer is responsible for installing. */
+ @GuardedBy("mLock")
+ private final SparseArrayMap<String, ArraySet<String>> mInstallers = new SparseArrayMap<>();
+
private volatile boolean mHasBattery = true;
private volatile boolean mIsEnabled;
private volatile int mBootPhase;
@@ -353,6 +358,14 @@ public class InternalResourceService extends SystemService {
return mCompleteEconomicPolicy;
}
+ /** Returns the number of apps that this app is expected to update at some point. */
+ int getAppUpdateResponsibilityCount(final int userId, @NonNull final String pkgName) {
+ synchronized (mLock) {
+ // TODO(248274798): return 0 if the app has lost the install permission
+ return ArrayUtils.size(mInstallers.get(userId, pkgName));
+ }
+ }
+
@NonNull
SparseArrayMap<String, InstalledPackageInfo> getInstalledPackages() {
synchronized (mLock) {
@@ -525,7 +538,8 @@ public class InternalResourceService extends SystemService {
}
synchronized (mLock) {
final InstalledPackageInfo ipo = new InstalledPackageInfo(packageInfo);
- mPkgCache.add(userId, pkgName, ipo);
+ final InstalledPackageInfo oldIpo = mPkgCache.add(userId, pkgName, ipo);
+ maybeUpdateInstallerStatusLocked(oldIpo, ipo);
mUidToPackageCache.add(uid, pkgName);
// TODO: only do this when the user first launches the app (app leaves stopped state)
mAgent.grantBirthrightLocked(userId, pkgName);
@@ -552,7 +566,14 @@ public class InternalResourceService extends SystemService {
synchronized (mLock) {
mUidToPackageCache.remove(uid, pkgName);
mVipOverrides.delete(userId, pkgName);
- mPkgCache.delete(userId, pkgName);
+ final InstalledPackageInfo ipo = mPkgCache.delete(userId, pkgName);
+ mInstallers.delete(userId, pkgName);
+ if (ipo != null && ipo.installerPackageName != null) {
+ final ArraySet<String> list = mInstallers.get(userId, ipo.installerPackageName);
+ if (list != null) {
+ list.remove(pkgName);
+ }
+ }
mAgent.onPackageRemovedLocked(userId, pkgName);
}
}
@@ -574,7 +595,8 @@ public class InternalResourceService extends SystemService {
mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
for (int i = pkgs.size() - 1; i >= 0; --i) {
final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
- mPkgCache.add(userId, ipo.packageName, ipo);
+ final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
+ maybeUpdateInstallerStatusLocked(oldIpo, ipo);
}
mAgent.grantBirthrightsLocked(userId);
}
@@ -590,6 +612,7 @@ public class InternalResourceService extends SystemService {
mUidToPackageCache.remove(pkgInfo.uid);
}
}
+ mInstallers.delete(userId);
mPkgCache.delete(userId);
mAgent.onUserRemovedLocked(userId);
}
@@ -746,8 +769,46 @@ public class InternalResourceService extends SystemService {
mPackageManager.getInstalledPackagesAsUser(PACKAGE_QUERY_FLAGS, userId);
for (int i = pkgs.size() - 1; i >= 0; --i) {
final InstalledPackageInfo ipo = new InstalledPackageInfo(pkgs.get(i));
- mPkgCache.add(userId, ipo.packageName, ipo);
+ final InstalledPackageInfo oldIpo = mPkgCache.add(userId, ipo.packageName, ipo);
+ maybeUpdateInstallerStatusLocked(oldIpo, ipo);
+ }
+ }
+ }
+
+ /**
+ * Used to update the set of installed apps for each installer. This only has an effect if the
+ * installer package name is different between {@code oldIpo} and {@code newIpo}.
+ */
+ @GuardedBy("mLock")
+ private void maybeUpdateInstallerStatusLocked(@Nullable InstalledPackageInfo oldIpo,
+ @NonNull InstalledPackageInfo newIpo) {
+ final boolean changed;
+ if (oldIpo == null) {
+ changed = newIpo.installerPackageName != null;
+ } else {
+ changed = !Objects.equals(oldIpo.installerPackageName, newIpo.installerPackageName);
+ }
+ if (!changed) {
+ return;
+ }
+ // InstallSourceInfo doesn't track userId, so for now, assume the installer on the package's
+ // user profile did the installation.
+ // TODO(246640162): use the actual installer's user ID
+ final int userId = UserHandle.getUserId(newIpo.uid);
+ final String pkgName = newIpo.packageName;
+ if (oldIpo != null) {
+ final ArraySet<String> oldList = mInstallers.get(userId, oldIpo.installerPackageName);
+ if (oldList != null) {
+ oldList.remove(pkgName);
+ }
+ }
+ if (newIpo.installerPackageName != null) {
+ ArraySet<String> newList = mInstallers.get(userId, newIpo.installerPackageName);
+ if (newList == null) {
+ newList = new ArraySet<>();
+ mInstallers.add(userId, newIpo.installerPackageName, newList);
}
+ newList.add(pkgName);
}
}
@@ -1360,6 +1421,23 @@ public class InternalResourceService extends SystemService {
pw.println();
pw.println();
+ pw.println("Installers:");
+ pw.increaseIndent();
+ for (int u = 0; u < mInstallers.numMaps(); ++u) {
+ final int userId = mInstallers.keyAt(u);
+
+ for (int p = 0; p < mInstallers.numElementsForKeyAt(u); ++p) {
+ final String pkgName = mInstallers.keyAt(u, p);
+
+ pw.print(appToString(userId, pkgName));
+ pw.print(": ");
+ pw.print(mInstallers.valueAt(u, p).size());
+ pw.println(" apps");
+ }
+ }
+ pw.decreaseIndent();
+
+ pw.println();
mCompleteEconomicPolicy.dump(pw);
pw.println();
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 55cc3520961e..322e824286d5 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -42,6 +42,7 @@ import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_
import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES;
import static android.app.tare.EconomyManager.DEFAULT_JS_REWARD_APP_INSTALL_MAX_CAKES;
@@ -87,6 +88,7 @@ import static android.app.tare.EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT;
import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED;
+import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER;
import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_INSTANT;
import static android.app.tare.EconomyManager.KEY_JS_REWARD_APP_INSTALL_MAX;
@@ -154,6 +156,7 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
private long mMinSatiatedBalanceExempted;
private long mMinSatiatedBalanceOther;
+ private long mMinSatiatedBalanceIncrementalAppUpdater;
private long mMaxSatiatedBalance;
private long mInitialSatiatedConsumptionLimit;
private long mHardSatiatedConsumptionLimit;
@@ -183,11 +186,20 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
if (mIrs.isPackageRestricted(userId, pkgName)) {
return 0;
}
+
+ final long baseBalance;
if (mIrs.isPackageExempted(userId, pkgName)) {
- return mMinSatiatedBalanceExempted;
+ baseBalance = mMinSatiatedBalanceExempted;
+ } else {
+ baseBalance = mMinSatiatedBalanceOther;
}
- // TODO: take other exemptions into account
- return mMinSatiatedBalanceOther;
+
+ long minBalance = baseBalance;
+
+ final int updateResponsibilityCount = mIrs.getAppUpdateResponsibilityCount(userId, pkgName);
+ minBalance += updateResponsibilityCount * mMinSatiatedBalanceIncrementalAppUpdater;
+
+ return Math.min(minBalance, mMaxSatiatedBalance);
}
@Override
@@ -242,6 +254,9 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
mMinSatiatedBalanceExempted = getConstantAsCake(mParser, properties,
KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES,
mMinSatiatedBalanceOther);
+ mMinSatiatedBalanceIncrementalAppUpdater = getConstantAsCake(mParser, properties,
+ KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+ DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
@@ -397,10 +412,11 @@ public class JobSchedulerEconomicPolicy extends EconomicPolicy {
@Override
void dump(IndentingPrintWriter pw) {
- pw.println("Min satiated balances:");
+ pw.println("Min satiated balance:");
pw.increaseIndent();
pw.print("Exempted", cakeToString(mMinSatiatedBalanceExempted)).println();
pw.print("Other", cakeToString(mMinSatiatedBalanceOther)).println();
+ pw.print("+App Updater", cakeToString(mMinSatiatedBalanceIncrementalAppUpdater)).println();
pw.decreaseIndent();
pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println();
pw.print("Consumption limits: [");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
index 620d1a0da76f..a68170c9bac7 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Ledger.java
@@ -286,7 +286,7 @@ class Ledger {
final int idx = (mRewardBucketIndex - b + NUM_REWARD_BUCKET_WINDOWS)
% NUM_REWARD_BUCKET_WINDOWS;
final RewardBucket rewardBucket = mRewardBuckets[idx];
- if (rewardBucket == null) {
+ if (rewardBucket == null || rewardBucket.startTimeMs == 0) {
continue;
}
diff --git a/core/api/current.txt b/core/api/current.txt
index efe119e08aa0..34a9026e618c 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4426,6 +4426,7 @@ package android.app {
method public void readFromParcel(android.os.Parcel);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.ActivityManager.MemoryInfo> CREATOR;
+ field public long advertisedMem;
field public long availMem;
field public boolean lowMemory;
field public long threshold;
@@ -11612,6 +11613,25 @@ package android.content.pm {
field public static final int STATUS_SUCCESS = 0; // 0x0
}
+ public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public android.graphics.Bitmap getIcon();
+ method @NonNull public String getLabel();
+ method @NonNull public android.icu.util.ULocale getLocale();
+ method @NonNull public String getPackageName();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageInstaller.PreapprovalDetails> CREATOR;
+ }
+
+ public static final class PackageInstaller.PreapprovalDetails.Builder {
+ ctor public PackageInstaller.PreapprovalDetails.Builder();
+ method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails build();
+ method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(@NonNull android.graphics.Bitmap);
+ method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(@NonNull String);
+ method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(@NonNull android.icu.util.ULocale);
+ method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(@NonNull String);
+ }
+
public static class PackageInstaller.Session implements java.io.Closeable {
method public void abandon();
method public void addChildSessionId(int);
@@ -11941,6 +11961,7 @@ package android.content.pm {
field @Deprecated public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
field public static final String FEATURE_CONTROLS = "android.software.controls";
+ field public static final String FEATURE_CREDENTIALS = "android.software.credentials";
field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
field public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
field public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
@@ -18644,8 +18665,10 @@ package android.hardware.lights {
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR;
field public static final int LIGHT_CAPABILITY_BRIGHTNESS = 1; // 0x1
- field public static final int LIGHT_CAPABILITY_RGB = 0; // 0x0
+ field public static final int LIGHT_CAPABILITY_COLOR_RGB = 2; // 0x2
+ field @Deprecated public static final int LIGHT_CAPABILITY_RGB = 0; // 0x0
field public static final int LIGHT_TYPE_INPUT = 10001; // 0x2711
+ field public static final int LIGHT_TYPE_KEYBOARD_BACKLIGHT = 10003; // 0x2713
field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8
field public static final int LIGHT_TYPE_PLAYER_ID = 10002; // 0x2712
}
@@ -22434,6 +22457,7 @@ package android.media {
field public static final String MIMETYPE_AUDIO_SCRAMBLED = "audio/scrambled";
field public static final String MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
field public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
+ field public static final String MIMETYPE_IMAGE_AVIF = "image/avif";
field public static final String MIMETYPE_TEXT_CEA_608 = "text/cea-608";
field public static final String MIMETYPE_TEXT_CEA_708 = "text/cea-708";
field public static final String MIMETYPE_TEXT_SUBRIP = "application/x-subrip";
@@ -51881,10 +51905,11 @@ package android.view.accessibility {
method public void setSpeechStateChangeTypes(int);
method public void writeToParcel(android.os.Parcel, int);
field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 1024; // 0x400
field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
- field public static final int CONTENT_CHANGE_TYPE_INVALID = 1024; // 0x400
+ field public static final int CONTENT_CHANGE_TYPE_ERROR = 2048; // 0x800
field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 7dcfab4b5503..f221eca45337 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3951,6 +3951,7 @@ package android.hardware.display {
}
public final class DisplayManager {
+ method @Nullable @RequiresPermission("android.permission.CAPTURE_VIDEO_OUTPUT") public static android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface);
method @RequiresPermission(android.Manifest.permission.ACCESS_AMBIENT_LIGHT_STATS) public java.util.List<android.hardware.display.AmbientBrightnessDayStats> getAmbientBrightnessStats();
method @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfiguration();
method @Nullable @RequiresPermission(android.Manifest.permission.CONFIGURE_DISPLAY_BRIGHTNESS) public android.hardware.display.BrightnessConfiguration getBrightnessConfigurationForDisplay(@NonNull String);
@@ -6672,15 +6673,6 @@ package android.media.musicrecognition {
}
-package android.media.projection {
-
- public class MediaProjectionGlobal {
- method @Nullable public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, @Nullable android.view.Surface);
- method @NonNull public static android.media.projection.MediaProjectionGlobal getInstance();
- }
-
-}
-
package android.media.session {
public final class MediaSessionManager {
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index fb2c9e43497f..e08cef92fc6c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -121,6 +121,7 @@ package android.app {
public class ActivityManager {
method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener);
method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName);
+ method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
method public long getTotalRam();
method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessCapabilities(int);
method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessState(int);
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 8ae16df64ace..576b572dcc9a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -29,6 +29,7 @@ import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -2812,6 +2813,15 @@ public class ActivityManager {
*/
public static class MemoryInfo implements Parcelable {
/**
+ * The advertised memory of the system, as the end user would encounter in a retail display
+ * environment. This value might be different from {@code totalMem}. This could be due to
+ * many reasons. For example, the ODM could reserve part of the memory for the Trusted
+ * Execution Environment (TEE) which the kernel doesn't have access or knowledge about it.
+ */
+ @SuppressLint("MutableBareField")
+ public long advertisedMem;
+
+ /**
* The available memory on the system. This number should not
* be considered absolute: due to the nature of the kernel, a significant
* portion of this memory is actually in use and needed for the overall
@@ -2860,6 +2870,7 @@ public class ActivityManager {
}
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(advertisedMem);
dest.writeLong(availMem);
dest.writeLong(totalMem);
dest.writeLong(threshold);
@@ -2871,6 +2882,7 @@ public class ActivityManager {
}
public void readFromParcel(Parcel source) {
+ advertisedMem = source.readLong();
availMem = source.readLong();
totalMem = source.readLong();
threshold = source.readLong();
@@ -4375,13 +4387,15 @@ public class ActivityManager {
* a profile, the {@link #getCurrentUser()}, the {@link UserHandle#SYSTEM system user}, or
* does not exist.
*
- * @param displayId id of the display, it must exist.
+ * @param displayId id of the display.
*
* @return whether the operation succeeded. Notice that if the user was already started in such
* display before, it will return {@code false}.
*
* @throws UnsupportedOperationException if the device does not support background users on
* secondary displays.
+ * @throws IllegalArgumentException if the display doesn't exist or is not a valid display to
+ * start secondary users on.
*
* @hide
*/
@@ -4402,6 +4416,24 @@ public class ActivityManager {
}
/**
+ * Gets the id of displays that can be used by
+ * {@link #startUserInBackgroundOnSecondaryDisplay(int, int)}.
+ *
+ * @hide
+ */
+ @TestApi
+ @Nullable
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_USERS})
+ public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+ try {
+ return getService().getSecondaryDisplayIdsForStartingBackgroundUsers();
+ } 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 419b8e1ef016..626b7d3cd5b1 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -30,6 +30,7 @@ import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PermissionMethod;
import android.content.pm.UserInfo;
import android.net.Uri;
import android.os.Bundle;
@@ -292,6 +293,7 @@ public abstract class ActivityManagerInternal {
boolean allowAll, int allowMode, String name, String callerPackage);
/** Checks if the calling binder pid as the permission. */
+ @PermissionMethod
public abstract void enforceCallingPermission(String permission, String func);
/** Returns the current user id. */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index cf5f10ba109e..7a9f3c1c4c76 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -6662,6 +6662,9 @@ public final class ActivityThread extends ClientTransactionHandler
// Pass the current context to HardwareRenderer
HardwareRenderer.setContextForInit(getSystemContext());
+ if (data.persistent) {
+ HardwareRenderer.setIsSystemOrPersistent();
+ }
// Instrumentation info affects the class loader, so load it before
// setting up the app context.
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index 980b79ba01e8..b4abd3c69482 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -770,4 +770,10 @@ interface IActivityManager {
"@android.annotation.RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}, conditional = true)")
boolean startUserInBackgroundOnSecondaryDisplay(int userid, int displayId);
+ /**
+ * Gets the ids of displays that can be used on {@link #startUserInBackgroundOnSecondaryDisplay(int userId, int displayId)}.
+ *
+ * <p>Typically used only by automotive builds when the vehicle has multiple displays.
+ */
+ @nullable int[] getSecondaryDisplayIdsForStartingBackgroundUsers();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9bbebc85255a..5695874e6100 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -4452,6 +4452,10 @@ public class Notification implements Parcelable
* <p>Apps targeting {@link Build.VERSION_CODES#Q} and above will have to request
* a permission ({@link android.Manifest.permission#USE_FULL_SCREEN_INTENT}) in order to
* use full screen intents.</p>
+ * <p>
+ * To be launched as a full screen intent, the notification must also be posted to a
+ * channel with importance level set to IMPORTANCE_HIGH or higher.
+ * </p>
*
* @param intent The pending intent to launch.
* @param highPriority Passing true will cause this notification to be sent
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index d4c9a42c8115..fadfa5cd68a5 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -445,8 +445,8 @@ public final class VirtualDeviceManager {
@Nullable Executor executor,
@Nullable AudioConfigurationChangeCallback callback) {
if (mVirtualAudioDevice == null) {
- mVirtualAudioDevice = new VirtualAudioDevice(
- mContext, mVirtualDevice, display, executor, callback);
+ mVirtualAudioDevice = new VirtualAudioDevice(mContext, mVirtualDevice, display,
+ executor, callback, () -> mVirtualAudioDevice = null);
}
return mVirtualAudioDevice;
}
diff --git a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
index 0db7b5fe8289..e200a117e475 100644
--- a/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
+++ b/core/java/android/companion/virtual/audio/VirtualAudioDevice.java
@@ -64,11 +64,24 @@ public final class VirtualAudioDevice implements Closeable {
void onRecordingConfigChanged(@NonNull List<AudioRecordingConfiguration> configs);
}
+ /**
+ * Interface to be notified when {@link #close()} is called.
+ *
+ * @hide
+ */
+ public interface CloseListener {
+ /**
+ * Notifies when {@link #close()} is called.
+ */
+ void onClosed();
+ }
+
private final Context mContext;
private final IVirtualDevice mVirtualDevice;
private final VirtualDisplay mVirtualDisplay;
private final AudioConfigurationChangeCallback mCallback;
private final Executor mExecutor;
+ private final CloseListener mListener;
@Nullable
private VirtualAudioSession mOngoingSession;
@@ -77,12 +90,13 @@ public final class VirtualAudioDevice implements Closeable {
*/
public VirtualAudioDevice(Context context, IVirtualDevice virtualDevice,
@NonNull VirtualDisplay virtualDisplay, @Nullable Executor executor,
- @Nullable AudioConfigurationChangeCallback callback) {
+ @Nullable AudioConfigurationChangeCallback callback, @Nullable CloseListener listener) {
mContext = context;
mVirtualDevice = virtualDevice;
mVirtualDisplay = virtualDisplay;
mExecutor = executor;
mCallback = callback;
+ mListener = listener;
}
/**
@@ -169,6 +183,10 @@ public final class VirtualAudioDevice implements Closeable {
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
+
+ if (mListener != null) {
+ mListener.onClosed();
+ }
}
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 97da2daf6e59..430b52c28f27 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -51,6 +51,7 @@ import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PermissionMethod;
import android.content.res.AssetManager;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
@@ -6066,6 +6067,7 @@ public abstract class Context {
*/
@CheckResult(suggest="#enforcePermission(String,int,int,String)")
@PackageManager.PermissionResult
+ @PermissionMethod
public abstract int checkPermission(@NonNull String permission, int pid, int uid);
/** @hide */
@@ -6098,6 +6100,7 @@ public abstract class Context {
*/
@CheckResult(suggest="#enforceCallingPermission(String,String)")
@PackageManager.PermissionResult
+ @PermissionMethod
public abstract int checkCallingPermission(@NonNull String permission);
/**
@@ -6118,6 +6121,7 @@ public abstract class Context {
*/
@CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)")
@PackageManager.PermissionResult
+ @PermissionMethod
public abstract int checkCallingOrSelfPermission(@NonNull String permission);
/**
@@ -6146,6 +6150,7 @@ public abstract class Context {
*
* @see #checkPermission(String, int, int)
*/
+ @PermissionMethod
public abstract void enforcePermission(
@NonNull String permission, int pid, int uid, @Nullable String message);
@@ -6167,6 +6172,7 @@ public abstract class Context {
*
* @see #checkCallingPermission(String)
*/
+ @PermissionMethod
public abstract void enforceCallingPermission(
@NonNull String permission, @Nullable String message);
@@ -6183,6 +6189,7 @@ public abstract class Context {
*
* @see #checkCallingOrSelfPermission(String)
*/
+ @PermissionMethod
public abstract void enforceCallingOrSelfPermission(
@NonNull String permission, @Nullable String message);
diff --git a/core/java/android/content/pm/PackageInstaller.aidl b/core/java/android/content/pm/PackageInstaller.aidl
index 270f870405f5..833919e16855 100644
--- a/core/java/android/content/pm/PackageInstaller.aidl
+++ b/core/java/android/content/pm/PackageInstaller.aidl
@@ -18,3 +18,4 @@ package android.content.pm;
parcelable PackageInstaller.SessionParams;
parcelable PackageInstaller.SessionInfo;
+parcelable PackageInstaller.PreapprovalDetails;
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 5f45c342a27c..5b182730b284 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -47,6 +47,7 @@ import android.content.pm.PackageManager.DeleteFlags;
import android.content.pm.PackageManager.InstallReason;
import android.content.pm.PackageManager.InstallScenario;
import android.graphics.Bitmap;
+import android.icu.util.ULocale;
import android.net.Uri;
import android.os.Build;
import android.os.FileBridge;
@@ -65,6 +66,7 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.ExceptionUtils;
+import com.android.internal.util.DataClass;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -3275,4 +3277,280 @@ public class PackageInstaller {
}
};
}
+
+ /**
+ * Details for requesting the pre-commit install approval.
+ */
+ @DataClass(genParcelable = true, genHiddenConstructor = true, genBuilder = true,
+ genToString = true)
+ public static final class PreapprovalDetails implements Parcelable {
+ /**
+ * The icon representing the app to be installed.
+ */
+ private final @Nullable Bitmap mIcon;
+ /**
+ * The label representing the app to be installed.
+ */
+ private final @NonNull String mLabel;
+ /**
+ * The locale of the app label being used.
+ */
+ private final @NonNull ULocale mLocale;
+ /**
+ * The package name of the app to be installed.
+ */
+ private final @NonNull String mPackageName;
+
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/PackageInstaller.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new PreapprovalDetails.
+ *
+ * @param icon
+ * The icon representing the app to be installed.
+ * @param label
+ * The label representing the app to be installed.
+ * @param locale
+ * The locale of the app label being used.
+ * @param packageName
+ * The package name of the app to be installed.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public PreapprovalDetails(
+ @Nullable Bitmap icon,
+ @NonNull String label,
+ @NonNull ULocale locale,
+ @NonNull String packageName) {
+ this.mIcon = icon;
+ this.mLabel = label;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLabel);
+ this.mLocale = locale;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLocale);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * The icon representing the app to be installed.
+ */
+ @DataClass.Generated.Member
+ public @Nullable Bitmap getIcon() {
+ return mIcon;
+ }
+
+ /**
+ * The label representing the app to be installed.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getLabel() {
+ return mLabel;
+ }
+
+ /**
+ * The locale of the app label being used.
+ */
+ @DataClass.Generated.Member
+ public @NonNull ULocale getLocale() {
+ return mLocale;
+ }
+
+ /**
+ * The package name of the app to be installed.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "PreapprovalDetails { " +
+ "icon = " + mIcon + ", " +
+ "label = " + mLabel + ", " +
+ "locale = " + mLocale + ", " +
+ "packageName = " + mPackageName +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mIcon != null) flg |= 0x1;
+ dest.writeByte(flg);
+ if (mIcon != null) mIcon.writeToParcel(dest, flags);
+ dest.writeString8(mLabel);
+ dest.writeString8(mLocale.toString());
+ dest.writeString8(mPackageName);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ PreapprovalDetails(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ Bitmap icon = (flg & 0x1) == 0 ? null : Bitmap.CREATOR.createFromParcel(in);
+ String label = in.readString8();
+ ULocale locale = new ULocale(in.readString8());
+ String packageName = in.readString8();
+
+ this.mIcon = icon;
+ this.mLabel = label;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLabel);
+ this.mLocale = locale;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLocale);
+ this.mPackageName = packageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mPackageName);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<PreapprovalDetails> CREATOR
+ = new Parcelable.Creator<PreapprovalDetails>() {
+ @Override
+ public PreapprovalDetails[] newArray(int size) {
+ return new PreapprovalDetails[size];
+ }
+
+ @Override
+ public PreapprovalDetails createFromParcel(@NonNull Parcel in) {
+ return new PreapprovalDetails(in);
+ }
+ };
+
+ /**
+ * A builder for {@link PreapprovalDetails}
+ */
+ @SuppressWarnings("WeakerAccess")
+ @DataClass.Generated.Member
+ public static final class Builder {
+
+ private @Nullable Bitmap mIcon;
+ private @NonNull String mLabel;
+ private @NonNull ULocale mLocale;
+ private @NonNull String mPackageName;
+
+ private long mBuilderFieldsSet = 0L;
+
+ /**
+ * Creates a new Builder.
+ */
+ public Builder() {}
+
+ /**
+ * The icon representing the app to be installed.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setIcon(@NonNull Bitmap value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x1;
+ mIcon = value;
+ return this;
+ }
+
+ /**
+ * The label representing the app to be installed.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLabel(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x2;
+ mLabel = value;
+ return this;
+ }
+
+ /**
+ * The locale of the app label being used.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setLocale(@NonNull ULocale value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mLocale = value;
+ return this;
+ }
+
+ /**
+ * The package name of the app to be installed.
+ */
+ @DataClass.Generated.Member
+ public @NonNull Builder setPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x8;
+ mPackageName = value;
+ return this;
+ }
+
+ /** Builds the instance. This builder should not be touched after calling this! */
+ public @NonNull PreapprovalDetails build() {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x10; // Mark builder used
+
+ PreapprovalDetails o = new PreapprovalDetails(
+ mIcon,
+ mLabel,
+ mLocale,
+ mPackageName);
+ return o;
+ }
+
+ private void checkNotUsed() {
+ if ((mBuilderFieldsSet & 0x10) != 0) {
+ throw new IllegalStateException(
+ "This Builder should not be reused. Use a new Builder instance instead");
+ }
+ }
+ }
+
+ @DataClass.Generated(
+ time = 1664257135109L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
+ inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.String mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+ }
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8e2a5eaff2d2..db991dcd3afc 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4187,6 +4187,13 @@ public abstract class PackageManager {
public static final String FEATURE_WINDOW_MAGNIFICATION =
"android.software.window_magnification";
+ /**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports retrieval of user credentials, via integration with credential providers.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_CREDENTIALS = "android.software.credentials";
+
/** @hide */
public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
diff --git a/core/java/android/content/pm/PermissionMethod.java b/core/java/android/content/pm/PermissionMethod.java
new file mode 100644
index 000000000000..021b2e1cd8f1
--- /dev/null
+++ b/core/java/android/content/pm/PermissionMethod.java
@@ -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 android.content.pm;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Documents that the subject method's job is to look
+ * up whether the provided or calling uid/pid has the requested permission.
+ *
+ * Methods should either return `void`, but potentially throw {@link SecurityException},
+ * or return {@link android.content.pm.PackageManager.PermissionResult} `int`.
+ *
+ * @hide
+ */
+@Retention(CLASS)
+@Target({METHOD})
+public @interface PermissionMethod {}
diff --git a/core/java/android/content/res/ResourceTimer.java b/core/java/android/content/res/ResourceTimer.java
index 13f0b72b8875..d51f64ce8106 100644
--- a/core/java/android/content/res/ResourceTimer.java
+++ b/core/java/android/content/res/ResourceTimer.java
@@ -19,6 +19,7 @@ package android.content.res;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppProtoEnums;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -30,6 +31,7 @@ import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.FrameworkStatsLog;
import java.io.FileOutputStream;
import java.io.PrintWriter;
@@ -111,6 +113,14 @@ public final class ResourceTimer {
private static Config sConfig;
/**
+ * This array contains the statsd enum associated with each timer entry. A value of NONE (0)
+ * means that the entry should not be logged to statsd. (This would be the case for timers
+ * that are created for temporary debugging.)
+ */
+ @GuardedBy("sLock")
+ private static int[] sApiMap;
+
+ /**
* A singleton Summary object that is refilled from the native side. The length of the array
* is the number of timers that can be fetched. nativeGetTimers() will fill the array to the
* smaller of the length of the array or the actual number of timers in the runtime. The
@@ -165,6 +175,19 @@ public final class ResourceTimer {
sTimers[i].percentile = new int[sConfig.maxBuckets];
sTimers[i].largest = new int[sConfig.maxLargest];
}
+ // Map the values returned from the runtime to statsd enumerals The runtime may
+ // return timers that are not meant to be logged via statsd. Such timers are mapped
+ // to RESOURCE_API_NONE.
+ sApiMap = new int[sConfig.maxTimer];
+ for (int i = 0; i < sApiMap.length; i++) {
+ if (sConfig.timers[i].equals("GetResourceValue")) {
+ sApiMap[i] = AppProtoEnums.RESOURCE_API_GET_VALUE;
+ } else if (sConfig.timers[i].equals("RetrieveAttributes")) {
+ sApiMap[i] = AppProtoEnums.RESOURCE_API_RETRIEVE_ATTRIBUTES;
+ } else {
+ sApiMap[i] = AppProtoEnums.RESOURCE_API_NONE;
+ }
+ }
sCurrentPoint = 0;
startTimer();
@@ -194,7 +217,9 @@ public final class ResourceTimer {
delay = sPublicationPoints[sCurrentPoint];
} else {
// Repeat with the final publication point.
- delay = sCurrentPoint * sPublicationPoints[sPublicationPoints.length - 1];
+ final long repeated = sPublicationPoints[sPublicationPoints.length - 1];
+ final int prelude = sPublicationPoints.length - 1;
+ delay = (sCurrentPoint - prelude) * repeated;
}
// Convert minutes to milliseconds.
delay *= 60 * 1000;
@@ -223,10 +248,19 @@ public final class ResourceTimer {
update(true);
// Log the number of records read. This happens a few times a day.
for (int i = 0; i < sTimers.length; i++) {
- if (sTimers[i].count > 0) {
+ var timer = sTimers[i];
+ if (timer.count > 0) {
Log.i(TAG, TextUtils.formatSimple("%s count=%d pvalues=%s",
- sConfig.timers[i], sTimers[i].count,
- packedString(sTimers[i].percentile)));
+ sConfig.timers[i], timer.count, packedString(timer.percentile)));
+ if (sApiMap[i] != AppProtoEnums.RESOURCE_API_NONE) {
+ FrameworkStatsLog.write(FrameworkStatsLog.RESOURCE_API_INFO,
+ sApiMap[i],
+ timer.count, timer.total,
+ timer.percentile[0], timer.percentile[1],
+ timer.percentile[2], timer.percentile[3],
+ timer.largest[0], timer.largest[1], timer.largest[2],
+ timer.largest[3], timer.largest[4]);
+ }
}
}
sCurrentPoint++;
diff --git a/core/java/android/hardware/DataSpace.java b/core/java/android/hardware/DataSpace.java
index 6c42776cca5e..15eae0920e7d 100644
--- a/core/java/android/hardware/DataSpace.java
+++ b/core/java/android/hardware/DataSpace.java
@@ -385,14 +385,6 @@ public final class DataSpace {
*/
public static final int RANGE_EXTENDED = 3 << 27;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = {
- DATASPACE_DEPTH,
- DATASPACE_DYNAMIC_DEPTH,
- })
- public @interface DataSpaceDepth {};
-
/**
* Depth.
*
@@ -407,13 +399,6 @@ public final class DataSpace {
*/
public static final int DATASPACE_DYNAMIC_DEPTH = 4098;
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(flag = true, value = {
- DATASPACE_HEIF,
- })
- public @interface DataSpaceFileFormat {};
-
/**
* High Efficiency Image File Format (HEIF).
*
@@ -442,7 +427,7 @@ public final class DataSpace {
DATASPACE_DCI_P3,
DATASPACE_SRGB_LINEAR
})
- public @interface NamedDataSpace {};
+ public @interface ColorDataSpace {};
/**
* Default-assumption data space, when not explicitly specified.
@@ -635,6 +620,30 @@ public final class DataSpace {
*/
public static final int DATASPACE_SRGB_LINEAR = 138477568;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, value = {
+ DATASPACE_DEPTH,
+ DATASPACE_DYNAMIC_DEPTH,
+ DATASPACE_HEIF,
+ DATASPACE_UNKNOWN,
+ DATASPACE_SCRGB_LINEAR,
+ DATASPACE_SRGB,
+ DATASPACE_SCRGB,
+ DATASPACE_DISPLAY_P3,
+ DATASPACE_BT2020_HLG,
+ DATASPACE_BT2020_PQ,
+ DATASPACE_ADOBE_RGB,
+ DATASPACE_JFIF,
+ DATASPACE_BT601_625,
+ DATASPACE_BT601_525,
+ DATASPACE_BT2020,
+ DATASPACE_BT709,
+ DATASPACE_DCI_P3,
+ DATASPACE_SRGB_LINEAR
+ })
+ public @interface NamedDataSpace {};
+
private DataSpace() {}
/**
@@ -647,7 +656,7 @@ public final class DataSpace {
*
* @return The int dataspace packed by standard, transfer and range value
*/
- public static @NamedDataSpace int pack(@DataSpaceStandard int standard,
+ public static @ColorDataSpace int pack(@DataSpaceStandard int standard,
@DataSpaceTransfer int transfer,
@DataSpaceRange int range) {
if ((standard & STANDARD_MASK) != standard) {
@@ -669,7 +678,7 @@ public final class DataSpace {
*
* @return The standard aspect
*/
- public static @DataSpaceStandard int getStandard(@NamedDataSpace int dataSpace) {
+ public static @DataSpaceStandard int getStandard(@ColorDataSpace int dataSpace) {
@DataSpaceStandard int standard = dataSpace & STANDARD_MASK;
return standard;
}
@@ -681,7 +690,7 @@ public final class DataSpace {
*
* @return The transfer aspect
*/
- public static @DataSpaceTransfer int getTransfer(@NamedDataSpace int dataSpace) {
+ public static @DataSpaceTransfer int getTransfer(@ColorDataSpace int dataSpace) {
@DataSpaceTransfer int transfer = dataSpace & TRANSFER_MASK;
return transfer;
}
@@ -693,7 +702,7 @@ public final class DataSpace {
*
* @return The range aspect
*/
- public static @DataSpaceRange int getRange(@NamedDataSpace int dataSpace) {
+ public static @DataSpaceRange int getRange(@ColorDataSpace int dataSpace) {
@DataSpaceRange int range = dataSpace & RANGE_MASK;
return range;
}
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 9e8703779863..90e92dbe2ab0 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -159,8 +159,9 @@ public final class OutputConfiguration implements Parcelable {
*
* <li> For a SurfaceView output surface, the timestamp base is {@link
* #TIMESTAMP_BASE_CHOREOGRAPHER_SYNCED}. The timestamp is overridden with choreographer
- * pulses from the display subsystem for smoother display of camera frames. The timestamp
- * is roughly in the same time base as {@link android.os.SystemClock#uptimeMillis}.</li>
+ * pulses from the display subsystem for smoother display of camera frames when the camera
+ * device runs in fixed frame rate. The timestamp is roughly in the same time base as
+ * {@link android.os.SystemClock#uptimeMillis}.</li>
* <li> For an output surface of MediaRecorder, MediaCodec, or ImageReader with {@link
* android.hardware.HardwareBuffer#USAGE_VIDEO_ENCODE} usge flag, the timestamp base is
* {@link #TIMESTAMP_BASE_MONOTONIC}, which is roughly the same time base as
@@ -231,7 +232,8 @@ public final class OutputConfiguration implements Parcelable {
*
* <p>The timestamp of the output images are overridden with choreographer pulses from the
* display subsystem for smoother display of camera frames. An output target of SurfaceView
- * uses this time base by default.</p>
+ * uses this time base by default. Note that the timestamp override is done for fixed camera
+ * frame rate only.</p>
*
* <p>This timestamp base isn't applicable to SurfaceTexture targets. SurfaceTexture's
* {@link android.graphics.SurfaceTexture#updateTexImage updateTexImage} function always
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index dfb42365357d..831119040842 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -33,6 +33,7 @@ import android.annotation.TestApi;
import android.app.KeyguardManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
+import android.content.pm.IPackageManager;
import android.content.res.Resources;
import android.graphics.Point;
import android.media.projection.MediaProjection;
@@ -40,6 +41,9 @@ import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.Looper;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
@@ -1315,6 +1319,61 @@ public final class DisplayManager {
}
/**
+ * Creates a VirtualDisplay that will mirror the content of displayIdToMirror
+ * @param name The name for the virtual display
+ * @param width The initial width for the virtual display
+ * @param height The initial height for the virtual display
+ * @param displayIdToMirror The displayId that will be mirrored into the virtual display.
+ * @return VirtualDisplay that can be used to update properties.
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT)
+ @Nullable
+ @SystemApi
+ public static VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height,
+ int displayIdToMirror, @Nullable Surface surface) {
+ IDisplayManager sDm = IDisplayManager.Stub.asInterface(
+ ServiceManager.getService(Context.DISPLAY_SERVICE));
+ IPackageManager sPackageManager = IPackageManager.Stub.asInterface(
+ ServiceManager.getService("package"));
+
+ // Density doesn't matter since this virtual display is only used for mirroring.
+ VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
+ height, 1 /* densityDpi */)
+ .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
+ .setDisplayIdToMirror(displayIdToMirror);
+ if (surface != null) {
+ builder.setSurface(surface);
+ }
+ VirtualDisplayConfig virtualDisplayConfig = builder.build();
+
+ String[] packages;
+ try {
+ packages = sPackageManager.getPackagesForUid(Process.myUid());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+
+ // Just use the first one since it just needs to match the package when looking it up by
+ // calling UID in system server.
+ // The call may come from a rooted device, in that case the requesting uid will be root so
+ // it will not have any package name
+ String packageName = packages == null ? null : packages[0];
+ DisplayManagerGlobal.VirtualDisplayCallback
+ callbackWrapper = new DisplayManagerGlobal.VirtualDisplayCallback(null, null);
+ int displayId;
+ try {
+ displayId = sDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper, null,
+ packageName);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ return DisplayManagerGlobal.getInstance().createVirtualDisplayWrapper(virtualDisplayConfig,
+ null, callbackWrapper, displayId);
+ }
+
+ /**
* Listens for changes in available display devices.
*/
public interface DisplayListener {
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index 9235ba15019c..3e509e4d1c2f 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -36,10 +36,12 @@ import android.sysprop.HdmiProperties;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ConcurrentUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@@ -69,6 +71,32 @@ public final class HdmiControlManager {
private static final int INVALID_PHYSICAL_ADDRESS = 0xFFFF;
/**
+ * A cache of the current device's physical address. When device's HDMI out port
+ * is not connected to any device, it is set to {@link #INVALID_PHYSICAL_ADDRESS}.
+ *
+ * <p>Otherwise it is updated by the {@link ClientHotplugEventListener} registered
+ * with {@link com.android.server.hdmi.HdmiControlService} by the
+ * {@link #addHotplugEventListener(HotplugEventListener)} and the address is from
+ * {@link com.android.server.hdmi.HdmiControlService#getPortInfo()}
+ */
+ @GuardedBy("mLock")
+ private int mLocalPhysicalAddress = INVALID_PHYSICAL_ADDRESS;
+
+ private void setLocalPhysicalAddress(int physicalAddress) {
+ synchronized (mLock) {
+ mLocalPhysicalAddress = physicalAddress;
+ }
+ }
+
+ private int getLocalPhysicalAddress() {
+ synchronized (mLock) {
+ return mLocalPhysicalAddress;
+ }
+ }
+
+ private final Object mLock = new Object();
+
+ /**
* Broadcast Action: Display OSD message.
* <p>Send when the service has a message to display on screen for events
* that need user's attention such as ARC status change.
@@ -1094,6 +1122,37 @@ public final class HdmiControlManager {
mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
mIsSwitchDevice = HdmiProperties.is_switch().orElse(false);
+ addHotplugEventListener(new ClientHotplugEventListener());
+ }
+
+ private final class ClientHotplugEventListener implements HotplugEventListener {
+
+ @Override
+ public void onReceived(HdmiHotplugEvent event) {
+ List<HdmiPortInfo> ports = new ArrayList<>();
+ try {
+ ports = mService.getPortInfo();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ if (ports.isEmpty()) {
+ Log.e(TAG, "Can't find port info, not updating connected status. "
+ + "Hotplug event:" + event);
+ return;
+ }
+ // If the HDMI OUT port is plugged or unplugged, update the mLocalPhysicalAddress
+ for (HdmiPortInfo port : ports) {
+ if (port.getId() == event.getPort()) {
+ if (port.getType() == HdmiPortInfo.PORT_OUTPUT) {
+ setLocalPhysicalAddress(
+ event.isConnected()
+ ? port.getAddress()
+ : INVALID_PHYSICAL_ADDRESS);
+ }
+ break;
+ }
+ }
+ }
}
private static boolean hasDeviceType(int[] types, int type) {
@@ -1464,11 +1523,7 @@ public final class HdmiControlManager {
* 1.4b 8.7 Physical Address for more details on the address discovery proccess.
*/
public int getPhysicalAddress() {
- try {
- return mService.getPhysicalAddress();
- } catch (RemoteException e) {
- return INVALID_PHYSICAL_ADDRESS;
- }
+ return getLocalPhysicalAddress();
}
/**
@@ -1482,7 +1537,7 @@ public final class HdmiControlManager {
*/
public boolean isDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
Objects.requireNonNull(targetDevice);
- int physicalAddress = getPhysicalAddress();
+ int physicalAddress = getLocalPhysicalAddress();
if (physicalAddress == INVALID_PHYSICAL_ADDRESS) {
return false;
}
@@ -1501,7 +1556,7 @@ public final class HdmiControlManager {
@Deprecated
public boolean isRemoteDeviceConnected(@NonNull HdmiDeviceInfo targetDevice) {
Objects.requireNonNull(targetDevice);
- int physicalAddress = getPhysicalAddress();
+ int physicalAddress = getLocalPhysicalAddress();
if (physicalAddress == INVALID_PHYSICAL_ADDRESS) {
return false;
}
diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java
index c3113799ad45..1df9b75f0b09 100644
--- a/core/java/android/hardware/lights/Light.java
+++ b/core/java/android/hardware/lights/Light.java
@@ -60,15 +60,29 @@ public final class Light implements Parcelable {
public static final int LIGHT_TYPE_PLAYER_ID = 10002;
/**
+ * Type for lights that illuminate keyboard keys.
+ */
+ public static final int LIGHT_TYPE_KEYBOARD_BACKLIGHT = 10003;
+
+ /**
* Capability for lights that could adjust its LED brightness. If the capability is not present
- * the led can only be turned either on or off.
+ * the LED can only be turned either on or off.
*/
public static final int LIGHT_CAPABILITY_BRIGHTNESS = 1 << 0;
/**
- * Capability for lights that has red, green and blue LEDs to control the light's color.
+ * Capability for lights that have red, green and blue LEDs to control the light's color.
+ */
+ public static final int LIGHT_CAPABILITY_COLOR_RGB = 1 << 1;
+
+ /**
+ * Capability for lights that have red, green and blue LEDs to control the light's color.
+ *
+ * @deprecated Wrong int based flag with value 0. Use capability flag {@code
+ * LIGHT_CAPABILITY_COLOR_RGB} instead.
*/
- public static final int LIGHT_CAPABILITY_RGB = 0 << 1;
+ @Deprecated
+ public static final int LIGHT_CAPABILITY_RGB = 0;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@@ -77,6 +91,7 @@ public final class Light implements Parcelable {
LIGHT_TYPE_MICROPHONE,
LIGHT_TYPE_INPUT,
LIGHT_TYPE_PLAYER_ID,
+ LIGHT_TYPE_KEYBOARD_BACKLIGHT,
})
public @interface LightType {}
@@ -85,6 +100,7 @@ public final class Light implements Parcelable {
@IntDef(flag = true, prefix = {"LIGHT_CAPABILITY_"},
value = {
LIGHT_CAPABILITY_BRIGHTNESS,
+ LIGHT_CAPABILITY_COLOR_RGB,
LIGHT_CAPABILITY_RGB,
})
public @interface LightCapability {}
@@ -233,7 +249,7 @@ public final class Light implements Parcelable {
* @return True if the hardware can control the RGB led, otherwise false.
*/
public boolean hasRgbControl() {
- return (mCapabilities & LIGHT_CAPABILITY_RGB) == LIGHT_CAPABILITY_RGB;
+ return (mCapabilities & LIGHT_CAPABILITY_COLOR_RGB) == LIGHT_CAPABILITY_COLOR_RGB;
}
}
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 8b3451e9cdcf..92088e9f0a33 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -65,6 +65,7 @@ import android.annotation.TestApi;
import android.annotation.UiContext;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
@@ -546,6 +547,20 @@ public class InputMethodService extends AbstractInputMethodService {
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.S)
public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // This is a bug id.
+ /**
+ * Disallow IMEs to override {@link InputMethodService#onCreateInputMethodSessionInterface()}
+ * method.
+ *
+ * <p>If IMEs targeting on Android U and beyond override the
+ * {@link InputMethodService#onCreateInputMethodSessionInterface()}, an {@link LinkageError}
+ * would be thrown.</p>
+ *
+ * @hide
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ private static final long DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE = 148086656L;
+
LayoutInflater mInflater;
TypedArray mThemeAttrs;
@UnsupportedAppUsage
@@ -1527,6 +1542,11 @@ public class InputMethodService extends AbstractInputMethodService {
}
@Override public void onCreate() {
+ if (methodIsOverridden("onCreateInputMethodSessionInterface")
+ && CompatChanges.isChangeEnabled(DISALLOW_INPUT_METHOD_INTERFACE_OVERRIDE)) {
+ throw new LinkageError("InputMethodService#onCreateInputMethodSessionInterface()"
+ + " can no longer be overridden!");
+ }
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.onCreate");
mTheme = Resources.selectSystemTheme(mTheme,
getApplicationInfo().targetSdkVersion,
@@ -1764,6 +1784,9 @@ public class InputMethodService extends AbstractInputMethodService {
* {@link InputMethodService#onDisplayCompletions(CompletionInfo[])},
* {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)},
* {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead.
+ *
+ * <p>IMEs targeting on Android U and above cannot override this method, or an
+ * {@link LinkageError} would be thrown.</p>
*/
@Deprecated
@Override
@@ -4067,4 +4090,13 @@ public class InputMethodService extends AbstractInputMethodService {
final KeyEvent upEvent = createBackKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking);
onKeyUp(KeyEvent.KEYCODE_BACK, upEvent);
}
+
+ private boolean methodIsOverridden(String methodName, Class<?>... parameterTypes) {
+ try {
+ return getClass().getMethod(methodName, parameterTypes).getDeclaringClass()
+ != InputMethodService.class;
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException("Method must exist.", e);
+ }
+ }
}
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index edfcb3d6f12a..d5c3de146015 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -54,6 +54,7 @@ import android.system.ErrnoException;
import android.system.Os;
import android.system.StructStat;
import android.text.TextUtils;
+import android.util.DataUnit;
import android.util.Log;
import android.util.Slog;
import android.webkit.MimeTypeMap;
@@ -83,6 +84,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -1309,6 +1311,85 @@ public final class FileUtils {
return val * pow;
}
+ private static long toBytes(long value, String unit) {
+ unit = unit.toUpperCase();
+
+ if (List.of("B").contains(unit)) {
+ return value;
+ }
+
+ if (List.of("K", "KB").contains(unit)) {
+ return DataUnit.KILOBYTES.toBytes(value);
+ }
+
+ if (List.of("M", "MB").contains(unit)) {
+ return DataUnit.MEGABYTES.toBytes(value);
+ }
+
+ if (List.of("G", "GB").contains(unit)) {
+ return DataUnit.GIGABYTES.toBytes(value);
+ }
+
+ if (List.of("KI", "KIB").contains(unit)) {
+ return DataUnit.KIBIBYTES.toBytes(value);
+ }
+
+ if (List.of("MI", "MIB").contains(unit)) {
+ return DataUnit.MEBIBYTES.toBytes(value);
+ }
+
+ if (List.of("GI", "GIB").contains(unit)) {
+ return DataUnit.GIBIBYTES.toBytes(value);
+ }
+
+ return Long.MIN_VALUE;
+ }
+
+ /**
+ * @param fmtSize The string that contains the size to be parsed. The
+ * expected format is:
+ *
+ * <p>"^((\\s*[-+]?[0-9]+)\\s*(B|K|KB|M|MB|G|GB|Ki|KiB|Mi|MiB|Gi|GiB)\\s*)$"
+ *
+ * <p>For example: 10Kb, 500GiB, 100mb. The unit is not case sensitive.
+ *
+ * @return the size in bytes. If {@code fmtSize} has invalid format, it
+ * returns {@link Long#MIN_VALUE}.
+ * @hide
+ */
+ public static long parseSize(@Nullable String fmtSize) {
+ if (fmtSize == null || fmtSize.isBlank()) {
+ return Long.MIN_VALUE;
+ }
+
+ int sign = 1;
+ fmtSize = fmtSize.trim();
+ char first = fmtSize.charAt(0);
+ if (first == '-' || first == '+') {
+ if (first == '-') {
+ sign = -1;
+ }
+
+ fmtSize = fmtSize.replace(first + "", "");
+ }
+
+ int index = 0;
+ // Find the last index of the value in fmtSize.
+ while (index < fmtSize.length() && Character.isDigit(fmtSize.charAt(index))) {
+ index++;
+ }
+
+ // Check if number and units are present.
+ if (index == 0 || index == fmtSize.length()) {
+ return Long.MIN_VALUE;
+ }
+
+ long value = sign * Long.valueOf(fmtSize.substring(0, index));
+ String unit = fmtSize.substring(index).trim();
+
+ return toBytes(value, unit);
+ }
+
/**
* Closes the given object quietly, ignoring any checked exceptions. Does
* nothing if the given object is {@code null}.
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 14082f3388a0..c943a3d1861c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -26,6 +26,7 @@ import android.annotation.TestApi;
import android.annotation.UptimeMillisLong;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build.VERSION_CODES;
+import android.sysprop.MemoryProperties;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -1330,6 +1331,24 @@ public class Process {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public static final native void sendSignalQuiet(int pid, int signal);
+ /**
+ * @return The advertised memory of the system, as the end user would encounter in a retail
+ * display environment. If the advertised memory is not defined, it returns
+ * {@code getTotalMemory()} rounded.
+ *
+ * @hide
+ */
+ public static final long getAdvertisedMem() {
+ String formatSize = MemoryProperties.memory_ddr_size().orElse("0KB");
+ long memSize = FileUtils.parseSize(formatSize);
+
+ if (memSize == Long.MIN_VALUE) {
+ return FileUtils.roundStorageSize(getTotalMemory());
+ }
+
+ return memSize;
+ }
+
/** @hide */
@UnsupportedAppUsage
public static final native long getFreeMemory();
diff --git a/core/java/android/service/credentials/Action.java b/core/java/android/service/credentials/Action.java
new file mode 100644
index 000000000000..186b2a60c430
--- /dev/null
+++ b/core/java/android/service/credentials/Action.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * An action defined by the provider that intents into the provider's app for specific
+ * user actions.
+ *
+ * @hide
+ */
+public final class Action implements Parcelable {
+ /** Info to be displayed with this action on the UI. */
+ private final @NonNull Slice mInfo;
+ /**
+ * The pending intent to be invoked when the user selects this action.
+ */
+ private final @NonNull PendingIntent mPendingIntent;
+
+ /**
+ * Constructs an action to be displayed on the UI.
+ *
+ * @param actionInfo The info to be displayed along with this action.
+ * @param pendingIntent The intent to be invoked when the user selects this action.
+ * @throws NullPointerException If {@code actionInfo}, or {@code pendingIntent} is null.
+ */
+ public Action(@NonNull Slice actionInfo, @NonNull PendingIntent pendingIntent) {
+ Objects.requireNonNull(actionInfo, "actionInfo must not be null");
+ Objects.requireNonNull(pendingIntent, "pendingIntent must not be null");
+ mInfo = actionInfo;
+ mPendingIntent = pendingIntent;
+ }
+
+ private Action(@NonNull Parcel in) {
+ mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
+ mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+ PendingIntent.class);
+ }
+
+ public static final @NonNull Creator<Action> CREATOR = new Creator<Action>() {
+ @Override
+ public Action createFromParcel(@NonNull Parcel in) {
+ return new Action(in);
+ }
+
+ @Override
+ public Action[] newArray(int size) {
+ return new Action[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mInfo.writeToParcel(dest, flags);
+ mPendingIntent.writeToParcel(dest, flags);
+ }
+
+ /**
+ * Returns the action info as a {@link Slice} object, to be displayed on the UI.
+ */
+ public @NonNull Slice getActionInfo() {
+ return mInfo;
+ }
+
+ /**
+ * Returns the {@link PendingIntent} to be invoked when the action is selected.
+ */
+ public @NonNull PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialCallback.java b/core/java/android/service/credentials/CreateCredentialCallback.java
new file mode 100644
index 000000000000..6108eea5bea1
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialCallback.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 android.service.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Callback to be invoked as a response to {@link CreateCredentialRequest}.
+ *
+ * @hide
+ */
+public final class CreateCredentialCallback {
+ private static final String TAG = "CreateCredentialCallback";
+
+ private final ICreateCredentialCallback mCallback;
+
+ /** @hide */
+ public CreateCredentialCallback(@NonNull ICreateCredentialCallback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Invoked on a successful response for {@link CreateCredentialRequest}
+ * @param response The response from the credential provider.
+ */
+ public void onSuccess(@NonNull CreateCredentialResponse response) {
+ try {
+ mCallback.onSuccess(response);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Invoked on a failure response for {@link CreateCredentialRequest}
+ * @param errorCode The code defining the type of error.
+ * @param message The message corresponding to the failure.
+ */
+ public void onFailure(int errorCode, @Nullable CharSequence message) {
+ Log.w(TAG, "onFailure: " + message);
+ try {
+ mCallback.onFailure(errorCode, message);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.aidl b/core/java/android/service/credentials/CreateCredentialRequest.aidl
new file mode 100644
index 000000000000..eb7fba9405f7
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable CreateCredentialRequest; \ No newline at end of file
diff --git a/core/java/android/service/credentials/CreateCredentialRequest.java b/core/java/android/service/credentials/CreateCredentialRequest.java
new file mode 100644
index 000000000000..ac11e04bcb77
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialRequest.java
@@ -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 android.service.credentials;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * Request for creating a credential.
+ *
+ * @hide
+ */
+public final class CreateCredentialRequest implements Parcelable {
+ private final @NonNull String mCallingPackage;
+ private final @NonNull String mType;
+ private final @NonNull Bundle mData;
+
+ /**
+ * Constructs a new instance.
+ *
+ * @throws IllegalArgumentException If {@code callingPackage}, or {@code type} string is
+ * null or empty.
+ * @throws NullPointerException If {@code data} is null.
+ */
+ public CreateCredentialRequest(@NonNull String callingPackage,
+ @NonNull String type, @NonNull Bundle data) {
+ mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage,
+ "callingPackage must not be null or empty");
+ mType = Preconditions.checkStringNotEmpty(type,
+ "type must not be null or empty");
+ mData = Objects.requireNonNull(data, "data must not be null");
+ }
+
+ private CreateCredentialRequest(@NonNull Parcel in) {
+ mCallingPackage = in.readString8();
+ mType = in.readString8();
+ mData = in.readBundle();
+ }
+
+ public static final @NonNull Creator<CreateCredentialRequest> CREATOR =
+ new Creator<CreateCredentialRequest>() {
+ @Override
+ public CreateCredentialRequest createFromParcel(@NonNull Parcel in) {
+ return new CreateCredentialRequest(in);
+ }
+
+ @Override
+ public CreateCredentialRequest[] newArray(int size) {
+ return new CreateCredentialRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mCallingPackage);
+ dest.writeString8(mType);
+ dest.writeBundle(mData);
+ }
+
+ /** Returns the calling package of the calling app. */
+ @NonNull
+ public String getCallingPackage() {
+ return mCallingPackage;
+ }
+
+ /** Returns the type of the credential to be created. */
+ @NonNull
+ public String getType() {
+ return mType;
+ }
+
+ /** Returns the data to be used while creating the credential. */
+ @NonNull
+ public Bundle getData() {
+ return mData;
+ }
+}
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.aidl b/core/java/android/service/credentials/CreateCredentialResponse.aidl
new file mode 100644
index 000000000000..73c9147b0ae4
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialResponse.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+parcelable CreateCredentialResponse;
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
new file mode 100644
index 000000000000..f2ad7272f207
--- /dev/null
+++ b/core/java/android/service/credentials/CreateCredentialResponse.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 android.service.credentials;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Response to a {@link CreateCredentialRequest}.
+ *
+ * @hide
+ */
+public final class CreateCredentialResponse implements Parcelable {
+ private final @Nullable CharSequence mHeader;
+ private final @NonNull List<SaveEntry> mSaveEntries;
+
+ private CreateCredentialResponse(@NonNull Parcel in) {
+ mHeader = in.readCharSequence();
+ mSaveEntries = in.createTypedArrayList(SaveEntry.CREATOR);
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeCharSequence(mHeader);
+ dest.writeTypedList(mSaveEntries);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<CreateCredentialResponse> CREATOR =
+ new Creator<CreateCredentialResponse>() {
+ @Override
+ public CreateCredentialResponse createFromParcel(@NonNull Parcel in) {
+ return new CreateCredentialResponse(in);
+ }
+
+ @Override
+ public CreateCredentialResponse[] newArray(int size) {
+ return new CreateCredentialResponse[size];
+ }
+ };
+
+ /* package-private */ CreateCredentialResponse(
+ @Nullable CharSequence header,
+ @NonNull List<SaveEntry> saveEntries) {
+ this.mHeader = header;
+ this.mSaveEntries = saveEntries;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSaveEntries);
+ }
+
+ /** Returns the header to be displayed on the UI. */
+ public @Nullable CharSequence getHeader() {
+ return mHeader;
+ }
+
+ /** Returns the list of save entries to be displayed on the UI. */
+ public @NonNull List<SaveEntry> getSaveEntries() {
+ return mSaveEntries;
+ }
+
+ /**
+ * A builder for {@link CreateCredentialResponse}
+ */
+ @SuppressWarnings("WeakerAccess")
+ public static final class Builder {
+
+ private @Nullable CharSequence mHeader;
+ private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
+
+ /** Sets the header to be displayed on the UI. */
+ public @NonNull Builder setHeader(@Nullable CharSequence header) {
+ mHeader = header;
+ return this;
+ }
+
+ /**
+ * Sets the list of save entries to be shown on the UI.
+ *
+ * @throws IllegalArgumentException If {@code saveEntries} is empty.
+ * @throws NullPointerException If {@code saveEntries} is null, or any of its elements
+ * are null.
+ */
+ public @NonNull Builder setSaveEntries(@NonNull List<SaveEntry> saveEntries) {
+ Preconditions.checkCollectionNotEmpty(saveEntries, "saveEntries");
+ mSaveEntries = Preconditions.checkCollectionElementsNotNull(
+ saveEntries, "saveEntries");
+ return this;
+ }
+
+ /**
+ * Adds an entry to the list of save entries to be shown on the UI.
+ *
+ * @throws NullPointerException If {@code saveEntry} is null.
+ */
+ public @NonNull Builder addSaveEntry(@NonNull SaveEntry saveEntry) {
+ mSaveEntries.add(Objects.requireNonNull(saveEntry));
+ return this;
+ }
+
+ /**
+ * Builds the instance.
+ *
+ * @throws IllegalArgumentException If {@code saveEntries} is empty.
+ */
+ public @NonNull CreateCredentialResponse build() {
+ Preconditions.checkCollectionNotEmpty(mSaveEntries, "saveEntries must "
+ + "not be empty");
+ return new CreateCredentialResponse(
+ mHeader,
+ mSaveEntries);
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/Credential.java b/core/java/android/service/credentials/Credential.java
new file mode 100644
index 000000000000..7d5da8a7c4e0
--- /dev/null
+++ b/core/java/android/service/credentials/Credential.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import static java.util.Objects.requireNonNull;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * A Credential object that contains type specific data that is returned from the credential
+ * provider to the framework. Framework then converts it to an app facing representation and
+ * returns to the calling app.
+ *
+ * @hide
+ */
+public final class Credential implements Parcelable {
+ /** The type of this credential. */
+ private final @NonNull String mType;
+
+ /** The data associated with this credential. */
+ private final @NonNull Bundle mData;
+
+ /**
+ * Constructs a credential object.
+ *
+ * @param type The type of the credential.
+ * @param data The data of the credential that is passed back to the framework, and eventually
+ * to the calling app.
+ * @throws NullPointerException If {@code data} is null.
+ * @throws IllegalArgumentException If {@code type} is null or empty.
+ */
+ public Credential(@NonNull String type, @NonNull Bundle data) {
+ Preconditions.checkStringNotEmpty(type, "type must not be null, or empty");
+ requireNonNull(data, "data must not be null");
+ this.mType = type;
+ this.mData = data;
+ }
+
+ private Credential(@NonNull Parcel in) {
+ mType = in.readString16NoHelper();
+ mData = in.readBundle();
+ }
+
+ /**
+ * Returns the type of the credential.
+ */
+ public @NonNull String getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the data associated with the credential.
+ */
+ public @NonNull Bundle getData() {
+ return mData;
+ }
+
+ public static final @NonNull Creator<Credential> CREATOR = new Creator<Credential>() {
+ @Override
+ public Credential createFromParcel(@NonNull Parcel in) {
+ return new Credential(in);
+ }
+
+ @Override
+ public Credential[] newArray(int size) {
+ return new Credential[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mType);
+ dest.writeBundle(mData);
+ }
+}
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
new file mode 100644
index 000000000000..b49215a08bfb
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * A credential entry that is displayed on the account selector UI. Each entry corresponds to
+ * something that the user can select.
+ *
+ * @hide
+ */
+public final class CredentialEntry implements Parcelable {
+ /** The type of the credential entry to be shown on the UI. */
+ private final @NonNull String mType;
+
+ /** The info to be displayed along with this credential entry on the UI. */
+ private final @NonNull Slice mInfo;
+
+ /** The pending intent to be invoked when this credential entry is selected. */
+ private final @Nullable PendingIntent mPendingIntent;
+
+ /**
+ * The underlying credential to be returned to the app when the user selects
+ * this credential entry.
+ */
+ private final @Nullable Credential mCredential;
+
+ /** A flag denoting whether auto-select is enabled for this entry. */
+ private final @NonNull boolean mAutoSelectAllowed;
+
+ private CredentialEntry(@NonNull String type, @NonNull Slice entryInfo,
+ @Nullable PendingIntent pendingIntent, @Nullable Credential credential,
+ @NonNull boolean autoSeletAllowed) {
+ mType = type;
+ mInfo = entryInfo;
+ mPendingIntent = pendingIntent;
+ mCredential = credential;
+ mAutoSelectAllowed = autoSeletAllowed;
+ }
+
+ private CredentialEntry(@NonNull Parcel in) {
+ mType = in.readString();
+ mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
+ mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+ PendingIntent.class);
+ mCredential = in.readParcelable(Credential.class.getClassLoader(),
+ Credential.class);
+ mAutoSelectAllowed = in.readBoolean();
+ }
+
+ public static final @NonNull Creator<CredentialEntry> CREATOR =
+ new Creator<CredentialEntry>() {
+ @Override
+ public CredentialEntry createFromParcel(@NonNull Parcel in) {
+ return new CredentialEntry(in);
+ }
+
+ @Override
+ public CredentialEntry[] newArray(int size) {
+ return new CredentialEntry[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString8(mType);
+ mInfo.writeToParcel(dest, flags);
+ mPendingIntent.writeToParcel(dest, flags);
+ mCredential.writeToParcel(dest, flags);
+ dest.writeBoolean(mAutoSelectAllowed);
+ }
+
+ /**
+ * Returns the specific credential type of the entry.
+ */
+ public @NonNull String getType() {
+ return mType;
+ }
+
+ /**
+ * Returns the UI info to be displayed for this entry.
+ */
+ public @NonNull Slice getInfo() {
+ return mInfo;
+ }
+
+ /**
+ * Returns the pending intent to be invoked if the user selects this entry.
+ */
+ public @Nullable PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ /**
+ * Returns the credential associated with this entry.
+ */
+ public @Nullable Credential getCredential() {
+ return mCredential;
+ }
+
+ /**
+ * Returns whether this entry can be auto selected if it is the only option for the user.
+ */
+ public @NonNull boolean isAutoSelectAllowed() {
+ return mAutoSelectAllowed;
+ }
+
+ /**
+ * Builder for {@link CredentialEntry}.
+ */
+ public static final class Builder {
+ private String mType;
+ private Slice mInfo;
+ private PendingIntent mPendingIntent;
+ private Credential mCredential;
+ private boolean mAutoSelectAllowed = false;
+
+ /**
+ * Builds the instance.
+ * @param type The type of credential underlying this credential entry.
+ * @param info The info to be displayed with this entry on the UI.
+ *
+ * @throws IllegalArgumentException If {@code type} is null or empty.
+ * @throws NullPointerException If {@code info} is null.
+ */
+ public Builder(@NonNull String type, @NonNull Slice info) {
+ mType = Preconditions.checkStringNotEmpty(type, "type must not be "
+ + "null, or empty");
+ mInfo = Objects.requireNonNull(info, "info must not be null");
+ }
+
+ /**
+ * Sets the pendingIntent to be invoked if the user selects this entry.
+ *
+ * @throws IllegalStateException If {@code credential} is already set. Must either set the
+ * {@code credential}, or the {@code pendingIntent}.
+ */
+ public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+ Preconditions.checkState(pendingIntent != null && mCredential != null,
+ "credential is already set. Cannot set both the pendingIntent "
+ + "and the credential");
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the credential to be used, if the user selects this entry.
+ *
+ * @throws IllegalStateException If {@code pendingIntent} is already set. Must either set
+ * the {@code pendingIntent}, or the {@code credential}.
+ */
+ public @NonNull Builder setCredential(@Nullable Credential credential) {
+ Preconditions.checkState(credential != null && mPendingIntent != null,
+ "pendingIntent is already set. Cannot set both the "
+ + "pendingIntent and the credential");
+ mCredential = credential;
+ return this;
+ }
+
+ /**
+ * Sets whether the entry is allowed to be auto selected by the framework.
+ * The default value is set to false.
+ */
+ public @NonNull Builder setAutoSelectAllowed(@NonNull boolean autoSelectAllowed) {
+ mAutoSelectAllowed = autoSelectAllowed;
+ return this;
+ }
+
+ /**
+ * Creates a new {@link CredentialEntry} instance.
+ *
+ * @throws NullPointerException If {@code info} is null.
+ * @throws IllegalArgumentException If {@code type} is null, or empty.
+ * @throws IllegalStateException If neither {@code pendingIntent} nor {@code credential}
+ * is set, or if both are set.
+ */
+ public @NonNull CredentialEntry build() {
+ Preconditions.checkState(mPendingIntent == null && mCredential == null,
+ "Either pendingIntent or credential must be set");
+ Preconditions.checkState(mPendingIntent != null && mCredential != null,
+ "Cannot set both the pendingIntent and credential");
+ return new CredentialEntry(mType, mInfo, mPendingIntent,
+ mCredential, mAutoSelectAllowed);
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
new file mode 100644
index 000000000000..1fe89dffaa0f
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.app.Service;
+import android.content.Intent;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Objects;
+
+/**
+ * Main service to be extended by credential providers, in order to return user credentials
+ * to the framework.
+ *
+ * @hide
+ */
+public abstract class CredentialProviderService extends Service {
+ private static final String TAG = "CredProviderService";
+ private Handler mHandler;
+
+ public static final String SERVICE_INTERFACE =
+ "android.service.credentials.CredentialProviderService";
+
+ @CallSuper
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mHandler = new Handler(Looper.getMainLooper(), null, true);
+ }
+
+ @Override
+ public final @NonNull IBinder onBind(@NonNull Intent intent) {
+ if (SERVICE_INTERFACE.equals(intent.getAction())) {
+ return mInterface.asBinder();
+ }
+ Log.i(TAG, "Failed to bind with intent: " + intent);
+ return null;
+ }
+
+ private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
+ @Override
+ public void onGetCredentials(GetCredentialsRequest request, ICancellationSignal transport,
+ IGetCredentialsCallback callback) throws RemoteException {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(transport);
+ Objects.requireNonNull(callback);
+
+ mHandler.sendMessage(obtainMessage(
+ CredentialProviderService::onGetCredentials,
+ CredentialProviderService.this, request,
+ CancellationSignal.fromTransport(transport),
+ new GetCredentialsCallback(callback)
+ ));
+ }
+
+ @Override
+ public void onCreateCredential(CreateCredentialRequest request,
+ ICancellationSignal transport, ICreateCredentialCallback callback)
+ throws RemoteException {
+ Objects.requireNonNull(request);
+ Objects.requireNonNull(transport);
+ Objects.requireNonNull(callback);
+
+ mHandler.sendMessage(obtainMessage(
+ CredentialProviderService::onCreateCredential,
+ CredentialProviderService.this, request,
+ CancellationSignal.fromTransport(transport),
+ new CreateCredentialCallback(callback)
+ ));
+ }
+ };
+
+ /**
+ * Called by the android system to retrieve user credentials from the connected provider
+ * service.
+ * @param request The credential request for the provider to handle.
+ * @param cancellationSignal Signal for providers to listen to any cancellation requests from
+ * the android system.
+ * @param callback Object used to relay the response of the credentials request.
+ */
+ public abstract void onGetCredentials(@NonNull GetCredentialsRequest request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull GetCredentialsCallback callback);
+
+ /**
+ * Called by the android system to create a credential.
+ * @param request The credential creation request for the provider to handle.
+ * @param cancellationSignal Signal for providers to listen to any cancellation requests from
+ * the android system.
+ * @param callback Object used to relay the response of the credential creation request.
+ */
+ public abstract void onCreateCredential(@NonNull CreateCredentialRequest request,
+ @NonNull CancellationSignal cancellationSignal,
+ @NonNull CreateCredentialCallback callback);
+}
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
new file mode 100644
index 000000000000..106f322c4ab4
--- /dev/null
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Content to be displayed on the account selector UI, including credential entries,
+ * actions etc.
+ *
+ * @hide
+ */
+public final class CredentialsDisplayContent implements Parcelable {
+ /** Header to be displayed on the UI. */
+ private final @Nullable CharSequence mHeader;
+
+ /** List of credential entries to be displayed on the UI. */
+ private final @NonNull List<CredentialEntry> mCredentialEntries;
+
+ /** List of provider actions to be displayed on the UI. */
+ private final @NonNull List<Action> mActions;
+
+ private CredentialsDisplayContent(@Nullable CharSequence header,
+ @NonNull List<CredentialEntry> credentialEntries,
+ @NonNull List<Action> actions) {
+ mHeader = header;
+ mCredentialEntries = credentialEntries;
+ mActions = actions;
+ }
+
+ private CredentialsDisplayContent(@NonNull Parcel in) {
+ mHeader = in.readCharSequence();
+ mCredentialEntries = in.createTypedArrayList(CredentialEntry.CREATOR);
+ mActions = in.createTypedArrayList(Action.CREATOR);
+ }
+
+ public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
+ new Creator<CredentialsDisplayContent>() {
+ @Override
+ public CredentialsDisplayContent createFromParcel(@NonNull Parcel in) {
+ return new CredentialsDisplayContent(in);
+ }
+
+ @Override
+ public CredentialsDisplayContent[] newArray(int size) {
+ return new CredentialsDisplayContent[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeCharSequence(mHeader);
+ dest.writeTypedList(mCredentialEntries);
+ dest.writeTypedList(mActions);
+ }
+
+ /**
+ * Returns the header to be displayed on the UI.
+ */
+ public @Nullable CharSequence getHeader() {
+ return mHeader;
+ }
+
+ /**
+ * Returns the list of credential entries to be displayed on the UI.
+ */
+ public @NonNull List<CredentialEntry> getCredentialEntries() {
+ return mCredentialEntries;
+ }
+
+ /**
+ * Returns the list of actions to be displayed on the UI.
+ */
+ public @NonNull List<Action> getActions() {
+ return mActions;
+ }
+
+ /**
+ * Builds an instance of {@link CredentialsDisplayContent}.
+ */
+ public static final class Builder {
+ private CharSequence mHeader = null;
+ private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
+ private List<Action> mActions = new ArrayList<>();
+
+ /**
+ * Sets the header to be displayed on the UI.
+ */
+ public @NonNull Builder setHeader(@Nullable CharSequence header) {
+ mHeader = header;
+ return this;
+ }
+
+ /**
+ * Adds a {@link CredentialEntry} to the list of entries to be displayed on
+ * the UI.
+ *
+ * @throws NullPointerException If the {@code credentialEntry} is null.
+ */
+ public @NonNull Builder addCredentialEntry(@NonNull CredentialEntry credentialEntry) {
+ mCredentialEntries.add(Objects.requireNonNull(credentialEntry));
+ return this;
+ }
+
+ /**
+ * Adds an {@link Action} to the list of actions to be displayed on
+ * the UI.
+ *
+ * @throws NullPointerException If {@code action} is null.
+ */
+ public @NonNull Builder addAction(@NonNull Action action) {
+ mActions.add(Objects.requireNonNull(action, "action must not be null"));
+ return this;
+ }
+
+ /**
+ * Sets the list of actions to be displayed on the UI.
+ *
+ * @throws NullPointerException If {@code actions} is null, or any of its elements
+ * is null.
+ */
+ public @NonNull Builder setActions(@NonNull List<Action> actions) {
+ mActions = Preconditions.checkCollectionElementsNotNull(actions,
+ "actions");
+ return this;
+ }
+
+ /**
+ * Sets the list of credential entries to be displayed on the
+ * account selector UI.
+ *
+ * @throws NullPointerException If {@code credentialEntries} is null, or any of its
+ * elements is null.
+ */
+ public @NonNull Builder setCredentialEntries(
+ @NonNull List<CredentialEntry> credentialEntries) {
+ mCredentialEntries = Preconditions.checkCollectionElementsNotNull(
+ credentialEntries,
+ "credentialEntries");
+ return this;
+ }
+
+ /**
+ * Builds a {@link GetCredentialsResponse} instance.
+ *
+ * @throws NullPointerException If {@code credentialEntries} is null.
+ * @throws IllegalStateException if both {@code credentialEntries} and
+ * {@code actions} are empty.
+ */
+ public @NonNull CredentialsDisplayContent build() {
+ if (mCredentialEntries != null && mCredentialEntries.isEmpty()
+ && mActions != null && mActions.isEmpty()) {
+ throw new IllegalStateException("credentialEntries and actions must not both "
+ + "be empty");
+ }
+ return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions);
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/GetCredentialOption.java b/core/java/android/service/credentials/GetCredentialOption.java
new file mode 100644
index 000000000000..c6cda1d1abf6
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialOption.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * A type specific credential request, containing the associated data to be used for
+ * retrieving credentials.
+ *
+ * @hide
+ */
+public final class GetCredentialOption implements Parcelable {
+ /** The type of credential requested. */
+ private final @NonNull String mType;
+
+ /** The data associated with the request. */
+ private final @NonNull Bundle mData;
+
+ /**
+ * Constructs a new instance of {@link GetCredentialOption}
+ *
+ * @throws IllegalArgumentException If {@code type} string is null or empty.
+ * @throws NullPointerException If {@code data} is null.
+ */
+ public GetCredentialOption(@NonNull String type, @NonNull Bundle data) {
+ Preconditions.checkStringNotEmpty(type, "type must not be null, or empty");
+ requireNonNull(data, "data must not be null");
+ mType = type;
+ mData = data;
+ }
+
+ /**
+ * Returns the data associated with this credential request option.
+ */
+ public @NonNull Bundle getData() {
+ return mData;
+ }
+
+ /**
+ * Returns the type associated with this credential request option.
+ */
+ public @NonNull String getType() {
+ return mType;
+ }
+
+ private GetCredentialOption(@NonNull Parcel in) {
+ mType = in.readString16NoHelper();
+ mData = in.readBundle();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString16NoHelper(mType);
+ dest.writeBundle(mData);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<GetCredentialOption> CREATOR =
+ new Creator<GetCredentialOption>() {
+ @Override
+ public GetCredentialOption createFromParcel(@NonNull Parcel in) {
+ return new GetCredentialOption(in);
+ }
+
+ @Override
+ public GetCredentialOption[] newArray(int size) {
+ return new GetCredentialOption[size];
+ }
+ };
+}
diff --git a/core/java/android/service/credentials/GetCredentialsCallback.java b/core/java/android/service/credentials/GetCredentialsCallback.java
new file mode 100644
index 000000000000..42a73946b5cc
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsCallback.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Callback to be invoked as a response to {@link GetCredentialsRequest}.
+ *
+ * @hide
+ */
+public final class GetCredentialsCallback {
+
+ private static final String TAG = "GetCredentialsCallback";
+
+ private final IGetCredentialsCallback mCallback;
+
+ /** @hide */
+ public GetCredentialsCallback(@NonNull IGetCredentialsCallback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Invoked on a successful response for {@link GetCredentialsRequest}
+ * @param response The response from the credential provider.
+ */
+ public void onSuccess(@NonNull GetCredentialsResponse response) {
+ try {
+ mCallback.onSuccess(response);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Invoked on a failure response for {@link GetCredentialsRequest}
+ * @param errorCode The code defining the kind of error.
+ * @param message The message corresponding to the failure.
+ */
+ public void onFailure(int errorCode, @Nullable CharSequence message) {
+ Log.w(TAG, "onFailure: " + message);
+ try {
+ mCallback.onFailure(errorCode, message);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.aidl b/core/java/android/service/credentials/GetCredentialsRequest.aidl
new file mode 100644
index 000000000000..b309d698e7de
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsRequest.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable GetCredentialsRequest; \ No newline at end of file
diff --git a/core/java/android/service/credentials/GetCredentialsRequest.java b/core/java/android/service/credentials/GetCredentialsRequest.java
new file mode 100644
index 000000000000..cf7c2834f75f
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsRequest.java
@@ -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 android.service.credentials;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request for getting user's credentials from a given credential provider.
+ *
+ * @hide
+ */
+public final class GetCredentialsRequest implements Parcelable {
+ /** Calling package of the app requesting for credentials. */
+ private final @NonNull String mCallingPackage;
+
+ /**
+ * List of credential options. Each {@link GetCredentialOption} object holds parameters to
+ * be used for retrieving specific type of credentials.
+ */
+ private final @NonNull List<GetCredentialOption> mGetCredentialOptions;
+
+ private GetCredentialsRequest(@NonNull String callingPackage,
+ @NonNull List<GetCredentialOption> getCredentialOptions) {
+ this.mCallingPackage = callingPackage;
+ this.mGetCredentialOptions = getCredentialOptions;
+ }
+
+ private GetCredentialsRequest(@NonNull Parcel in) {
+ mCallingPackage = in.readString16NoHelper();
+ mGetCredentialOptions = in.createTypedArrayList(GetCredentialOption.CREATOR);
+ }
+
+ public static final @NonNull Creator<GetCredentialsRequest> CREATOR =
+ new Creator<GetCredentialsRequest>() {
+ @Override
+ public GetCredentialsRequest createFromParcel(Parcel in) {
+ return new GetCredentialsRequest(in);
+ }
+
+ @Override
+ public GetCredentialsRequest[] newArray(int size) {
+ return new GetCredentialsRequest[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString16NoHelper(mCallingPackage);
+ dest.writeTypedList(mGetCredentialOptions);
+ }
+
+ /**
+ * Returns the calling package of the app requesting credentials.
+ */
+ public @NonNull String getCallingPackage() {
+ return mCallingPackage;
+ }
+
+ /**
+ * Returns the list of type specific credential options to return credentials for.
+ */
+ public @NonNull List<GetCredentialOption> getGetCredentialOptions() {
+ return mGetCredentialOptions;
+ }
+
+ /**
+ * Builder for {@link GetCredentialsRequest}.
+ */
+ public static final class Builder {
+ private String mCallingPackage;
+ private List<GetCredentialOption> mGetCredentialOptions = new ArrayList<>();
+
+ /**
+ * Creates a new builder.
+ * @param callingPackage The calling package of the app requesting credentials.
+ *
+ * @throws IllegalArgumentException If {@code callingPackag}e is null or empty.
+ */
+ public Builder(@NonNull String callingPackage) {
+ mCallingPackage = Preconditions.checkStringNotEmpty(callingPackage);
+ }
+
+ /**
+ * Sets the list of credential options.
+ *
+ * @throws NullPointerException If {@code getCredentialOptions} itself or any of its
+ * elements is null.
+ * @throws IllegalArgumentException If {@code getCredentialOptions} is empty.
+ */
+ public @NonNull Builder setGetCredentialOptions(
+ @NonNull List<GetCredentialOption> getCredentialOptions) {
+ Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
+ "getCredentialOptions");
+ Preconditions.checkCollectionElementsNotNull(mGetCredentialOptions,
+ "getCredentialOptions");
+ mGetCredentialOptions = getCredentialOptions;
+ return this;
+ }
+
+ /**
+ * Adds a single {@link GetCredentialOption} object to the list of credential options.
+ *
+ * @throws NullPointerException If {@code getCredentialOption} is null.
+ */
+ public @NonNull Builder addGetCredentialOption(
+ @NonNull GetCredentialOption getCredentialOption) {
+ Objects.requireNonNull(getCredentialOption,
+ "getCredentialOption must not be null");
+ mGetCredentialOptions.add(getCredentialOption);
+ return this;
+ }
+
+ /**
+ * Builds a new {@link GetCredentialsRequest} instance.
+ *
+ * @throws NullPointerException If {@code getCredentialOptions} is null.
+ * @throws IllegalArgumentException If {@code getCredentialOptions} is empty, or if
+ * {@code callingPackage} is null or empty.
+ */
+ public @NonNull GetCredentialsRequest build() {
+ Preconditions.checkStringNotEmpty(mCallingPackage,
+ "Must set the calling package");
+ Preconditions.checkCollectionNotEmpty(mGetCredentialOptions,
+ "getCredentialOptions");
+ return new GetCredentialsRequest(mCallingPackage, mGetCredentialOptions);
+ }
+ }
+}
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.aidl b/core/java/android/service/credentials/GetCredentialsResponse.aidl
new file mode 100644
index 000000000000..0d8c6357a715
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsResponse.aidl
@@ -0,0 +1,3 @@
+package android.service.credentials;
+
+parcelable GetCredentialsResponse; \ No newline at end of file
diff --git a/core/java/android/service/credentials/GetCredentialsResponse.java b/core/java/android/service/credentials/GetCredentialsResponse.java
new file mode 100644
index 000000000000..293867ba55b2
--- /dev/null
+++ b/core/java/android/service/credentials/GetCredentialsResponse.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import java.util.Objects;
+
+/**
+ * Response from a credential provider, containing credential entries and other associated
+ * data to be shown on the account selector UI.
+ *
+ * @hide
+ */
+public final class GetCredentialsResponse implements Parcelable {
+ /** Content to be used for the UI. */
+ private final @Nullable CredentialsDisplayContent mCredentialsDisplayContent;
+
+ /**
+ * Authentication action that must be launched and completed before showing any content
+ * from the provider.
+ */
+ private final @Nullable Action mAuthenticationAction;
+
+ /**
+ * Creates a {@link GetCredentialsRequest} instance with an authentication action set.
+ * Providers must use this method when no content can be shown before authentication.
+ *
+ * @throws NullPointerException If {@code authenticationAction} is null.
+ */
+ public static @NonNull GetCredentialsResponse createWithAuthentication(
+ @NonNull Action authenticationAction) {
+ Objects.requireNonNull(authenticationAction,
+ "authenticationAction must not be null");
+ return new GetCredentialsResponse(null, authenticationAction);
+ }
+
+ /**
+ * Creates a {@link GetCredentialsRequest} instance with display content to be shown on the UI.
+ * Providers must use this method when there is content to be shown without top level
+ * authentication required.
+ *
+ * @throws NullPointerException If {@code credentialsDisplayContent} is null.
+ */
+ public static @NonNull GetCredentialsResponse createWithDisplayContent(
+ @NonNull CredentialsDisplayContent credentialsDisplayContent) {
+ Objects.requireNonNull(credentialsDisplayContent,
+ "credentialsDisplayContent must not be null");
+ return new GetCredentialsResponse(credentialsDisplayContent, null);
+ }
+
+ private GetCredentialsResponse(@Nullable CredentialsDisplayContent credentialsDisplayContent,
+ @Nullable Action authenticationAction) {
+ mCredentialsDisplayContent = credentialsDisplayContent;
+ mAuthenticationAction = authenticationAction;
+ }
+
+ private GetCredentialsResponse(@NonNull Parcel in) {
+ mCredentialsDisplayContent = in.readParcelable(CredentialsDisplayContent.class
+ .getClassLoader(), CredentialsDisplayContent.class);
+ mAuthenticationAction = in.readParcelable(Action.class.getClassLoader(), Action.class);
+ }
+
+ public static final @NonNull Creator<GetCredentialsResponse> CREATOR =
+ new Creator<GetCredentialsResponse>() {
+ @Override
+ public GetCredentialsResponse createFromParcel(Parcel in) {
+ return new GetCredentialsResponse(in);
+ }
+
+ @Override
+ public GetCredentialsResponse[] newArray(int size) {
+ return new GetCredentialsResponse[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mCredentialsDisplayContent, flags);
+ dest.writeParcelable(mAuthenticationAction, flags);
+ }
+
+ /**
+ * Returns whether the response contains a top level authentication action.
+ */
+ public @NonNull boolean isAuthenticationActionSet() {
+ return mAuthenticationAction != null;
+ }
+
+ /**
+ * Returns the authentication action to be invoked before any other content
+ * can be shown to the user.
+ */
+ public @NonNull Action getAuthenticationAction() {
+ return mAuthenticationAction;
+ }
+
+ /**
+ * Returns the credentialDisplayContent that does not require authentication, and
+ * can be shown to the user on the account selector UI.
+ */
+ public @NonNull CredentialsDisplayContent getCredentialsDisplayContent() {
+ return mCredentialsDisplayContent;
+ }
+}
diff --git a/core/java/android/service/credentials/ICreateCredentialCallback.aidl b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
new file mode 100644
index 000000000000..4cc76a4a341b
--- /dev/null
+++ b/core/java/android/service/credentials/ICreateCredentialCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.CreateCredentialResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface ICreateCredentialCallback {
+ void onSuccess(in CreateCredentialResponse request);
+ void onFailure(int errorCode, in CharSequence message);
+} \ No newline at end of file
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
new file mode 100644
index 000000000000..c68430ce752e
--- /dev/null
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -0,0 +1,33 @@
+/*
+ * 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.service.credentials;
+
+import android.os.ICancellationSignal;
+import android.service.credentials.GetCredentialsRequest;
+import android.service.credentials.CreateCredentialRequest;
+import android.service.credentials.IGetCredentialsCallback;
+import android.service.credentials.ICreateCredentialCallback;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface ICredentialProviderService {
+ void onGetCredentials(in GetCredentialsRequest request, in ICancellationSignal transport, in IGetCredentialsCallback callback);
+ void onCreateCredential(in CreateCredentialRequest request, in ICancellationSignal transport, in ICreateCredentialCallback callback);
+}
diff --git a/core/java/android/service/credentials/IGetCredentialsCallback.aidl b/core/java/android/service/credentials/IGetCredentialsCallback.aidl
new file mode 100644
index 000000000000..6e20c555af60
--- /dev/null
+++ b/core/java/android/service/credentials/IGetCredentialsCallback.aidl
@@ -0,0 +1,13 @@
+package android.service.credentials;
+
+import android.service.credentials.GetCredentialsResponse;
+
+/**
+ * Interface from the system to a credential provider service.
+ *
+ * @hide
+ */
+oneway interface IGetCredentialsCallback {
+ void onSuccess(in GetCredentialsResponse response);
+ void onFailure(int errorCode, in CharSequence message);
+} \ No newline at end of file
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/SaveEntry.java
new file mode 100644
index 000000000000..28fec30caa89
--- /dev/null
+++ b/core/java/android/service/credentials/SaveEntry.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.credentials;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * An entry to be shown on the UI. This entry represents where the credential to be created will
+ * be stored. Examples include user's account, family group etc.
+ *
+ * @hide
+ */
+public final class SaveEntry implements Parcelable {
+ private final @NonNull Slice mInfo;
+ private final @Nullable PendingIntent mPendingIntent;
+ private final @Nullable Credential mCredential;
+
+ private SaveEntry(@NonNull Parcel in) {
+ mInfo = in.readParcelable(Slice.class.getClassLoader(), Slice.class);
+ mPendingIntent = in.readParcelable(PendingIntent.class.getClassLoader(),
+ PendingIntent.class);
+ mCredential = in.readParcelable(Credential.class.getClassLoader(), Credential.class);
+ }
+
+ public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
+ @Override
+ public SaveEntry createFromParcel(@NonNull Parcel in) {
+ return new SaveEntry(in);
+ }
+
+ @Override
+ public SaveEntry[] newArray(int size) {
+ return new SaveEntry[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ mInfo.writeToParcel(dest, flags);
+ mPendingIntent.writeToParcel(dest, flags);
+ mCredential.writeToParcel(dest, flags);
+ }
+
+ /* package-private */ SaveEntry(
+ @NonNull Slice info,
+ @Nullable PendingIntent pendingIntent,
+ @Nullable Credential credential) {
+ this.mInfo = info;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mInfo);
+ this.mPendingIntent = pendingIntent;
+ this.mCredential = credential;
+ }
+
+ /** Returns the info to be displayed with this save entry on the UI. */
+ public @NonNull Slice getInfo() {
+ return mInfo;
+ }
+
+ /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
+ public @Nullable PendingIntent getPendingIntent() {
+ return mPendingIntent;
+ }
+
+ /** Returns the credential produced by the {@link CreateCredentialRequest}. */
+ public @Nullable Credential getCredential() {
+ return mCredential;
+ }
+
+ /**
+ * A builder for {@link SaveEntry}.
+ */
+ public static final class Builder {
+
+ private @NonNull Slice mInfo;
+ private @Nullable PendingIntent mPendingIntent;
+ private @Nullable Credential mCredential;
+
+ /**
+ * Builds the instance.
+ * @param info The info to be displayed with this save entry.
+ *
+ * @throws NullPointerException If {@code info} is null.
+ */
+ public Builder(@NonNull Slice info) {
+ mInfo = Objects.requireNonNull(info, "info must not be null");
+ }
+
+ /**
+ * Sets the pendingIntent to be invoked when this entry is selected by the user.
+ *
+ * @throws IllegalStateException If {@code credential} is already set. Must only set either
+ * {@code credential}, or the {@code pendingIntent}.
+ */
+ public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
+ Preconditions.checkState(pendingIntent != null
+ && mCredential != null, "credential is already set. Must only set "
+ + "either the pendingIntent or the credential");
+ mPendingIntent = pendingIntent;
+ return this;
+ }
+
+ /**
+ * Sets the credential to be returned when this entry is selected by the user.
+ *
+ * @throws IllegalStateException If {@code pendingIntent} is already set. Must only
+ * set either the {@code pendingIntent}, or {@code credential}.
+ */
+ public @NonNull Builder setCredential(@Nullable Credential credential) {
+ Preconditions.checkState(credential != null && mPendingIntent != null,
+ "pendingIntent is already set. Must only set either the credential "
+ + "or the pendingIntent");
+ mCredential = credential;
+ return this;
+ }
+
+ /**
+ * Builds the instance.
+ *
+ * @throws IllegalStateException if both {@code pendingIntent} and {@code credential}
+ * are null.
+ */
+ public @NonNull SaveEntry build() {
+ Preconditions.checkState(mPendingIntent == null && mCredential == null,
+ "pendingIntent and credential both must not be null. Must set "
+ + "either the pendingIntnet or the credential");
+ return new SaveEntry(
+ mInfo,
+ mPendingIntent,
+ mCredential);
+ }
+ }
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 1b46107de02c..1285d1e2a4eb 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -345,6 +345,12 @@ public class VoiceInteractionService extends Service {
* Calling this a second time invalidates the previously created hotword detector
* which can no longer be used to manage recognition.
*
+ * <p>Note: If there are any active detectors that are created by using
+ * {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+ * AlwaysOnHotwordDetector.Callback)} or {@link #createHotwordDetector(PersistableBundle,
+ * SharedMemory, HotwordDetector.Callback)}, call this will throw an
+ * {@link IllegalArgumentException}.
+ *
* @param keyphrase The keyphrase that's being used, for example "Hello Android".
* @param locale The locale for which the enrollment needs to be performed.
* @param callback The callback to notify of detection events.
@@ -377,6 +383,10 @@ public class VoiceInteractionService extends Service {
* <p>Note: The system will trigger hotword detection service after calling this function when
* all conditions meet the requirements.
*
+ * <p>Note: If there are any active detectors that are created by using
+ * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
+ * call this will throw an {@link IllegalArgumentException}.
+ *
* @param keyphrase The keyphrase that's being used, for example "Hello Android".
* @param locale The locale for which the enrollment needs to be performed.
* @param options Application configuration data provided by the
@@ -420,6 +430,14 @@ public class VoiceInteractionService extends Service {
safelyShutdownAllHotwordDetectors();
}
+ for (HotwordDetector detector : mActiveHotwordDetectors) {
+ if (detector.isUsingHotwordDetectionService() != supportHotwordDetectionService) {
+ throw new IllegalArgumentException(
+ "It disallows to create trusted and non-trusted detectors "
+ + "at the same time.");
+ }
+ }
+
AlwaysOnHotwordDetector dspDetector = new AlwaysOnHotwordDetector(keyphrase, locale,
callback, mKeyphraseEnrollmentInfo, mSystemService,
getApplicationContext().getApplicationInfo().targetSdkVersion,
@@ -460,6 +478,10 @@ public class VoiceInteractionService extends Service {
* devices where hardware filtering is available (such as through a DSP), it's highly
* recommended to use {@link #createAlwaysOnHotwordDetector} instead.
*
+ * <p>Note: If there are any active detectors that are created by using
+ * {@link #createAlwaysOnHotwordDetector(String, Locale, AlwaysOnHotwordDetector.Callback)},
+ * call this will throw an {@link IllegalArgumentException}.
+ *
* @param options Application configuration data to be provided to the
* {@link HotwordDetectionService}. PersistableBundle does not allow any remotable objects or
* other contents that can be used to communicate with other processes.
@@ -490,7 +512,11 @@ public class VoiceInteractionService extends Service {
safelyShutdownAllHotwordDetectors();
} else {
for (HotwordDetector detector : mActiveHotwordDetectors) {
- if (detector instanceof SoftwareHotwordDetector) {
+ if (!detector.isUsingHotwordDetectionService()) {
+ throw new IllegalArgumentException(
+ "It disallows to create trusted and non-trusted detectors "
+ + "at the same time.");
+ } else if (detector instanceof SoftwareHotwordDetector) {
throw new IllegalArgumentException(
"There is already an active SoftwareHotwordDetector. "
+ "It must be destroyed to create a new one.");
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index b2a26fa665f8..067946e90361 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -67,6 +67,7 @@ import android.view.SurfaceControl;
import android.view.displayhash.DisplayHash;
import android.view.displayhash.VerifiedDisplayHash;
import android.window.ITaskFpsCallback;
+import android.window.ScreenCapture;
/**
* System private interface to the window manager.
@@ -968,4 +969,11 @@ interface IWindowManager
* treatment.
*/
boolean isLetterboxBackgroundMultiColored();
+
+ /**
+ * Captures the entire display specified by the displayId using the args provided. If the args
+ * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.
+ */
+ oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,
+ in ScreenCapture.ScreenCaptureListener listener);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index 2f7ae498b8b1..3ffb78161da1 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -57,7 +57,6 @@ import android.hardware.display.IDisplayManager;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplay;
import android.hardware.graphics.common.DisplayDecorationSupport;
-import android.media.projection.MediaProjectionGlobal;
import android.opengl.EGLDisplay;
import android.opengl.EGLSync;
import android.os.Build;
@@ -2015,8 +2014,8 @@ public final class SurfaceControl implements Parcelable {
}
// We don't have a size yet so pass in 1 for width and height since 0 is invalid
- VirtualDisplay vd = MediaProjectionGlobal.getInstance().createVirtualDisplay(name,
- 1 /* width */, 1 /* height */, INVALID_DISPLAY, null /* Surface */);
+ VirtualDisplay vd = DisplayManager.createVirtualDisplay(name, 1 /* width */, 1 /* height */,
+ INVALID_DISPLAY, null /* Surface */);
return vd == null ? null : vd.getToken().asBinder();
}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index c97eb73c120b..d77e8825e462 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -196,8 +196,6 @@ public final class ThreadedRenderer extends HardwareRenderer {
*/
public static boolean sRendererEnabled = true;
- public static boolean sTrimForeground = false;
-
/**
* Controls whether or not the renderer should aggressively trim
* memory. Note that this must not be set for any process that uses
@@ -205,9 +203,10 @@ public final class ThreadedRenderer extends HardwareRenderer {
* that do not go into the background.
*/
public static void enableForegroundTrimming() {
- sTrimForeground = true;
+ // TODO: Remove
}
+
/**
* Initialize HWUI for being in a system process like system_server
* Should not be called in non-system processes
@@ -218,9 +217,8 @@ public final class ThreadedRenderer extends HardwareRenderer {
// process.
if (!ActivityManager.isHighEndGfx()) {
sRendererEnabled = false;
- } else {
- enableForegroundTrimming();
}
+ setIsSystemOrPersistent();
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index b6f775d3e536..28fa77c0a4de 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1766,9 +1766,6 @@ public final class ViewRootImpl implements ViewParent,
mAppVisibilityChanged = true;
scheduleTraversals();
}
- if (!mAppVisible) {
- WindowManagerGlobal.trimForeground();
- }
AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
}
}
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index d37756551db3..4a9dc5b14a71 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -19,9 +19,7 @@ package android.view;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.ActivityManager;
import android.compat.annotation.UnsupportedAppUsage;
-import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
@@ -524,9 +522,6 @@ public final class WindowManagerGlobal {
}
allViewsRemoved = mRoots.isEmpty();
}
- if (ThreadedRenderer.sTrimForeground) {
- doTrimForeground();
- }
// If we don't have any views anymore in our process, we no longer need the
// InsetsAnimationThread to save some resources.
@@ -543,65 +538,9 @@ public final class WindowManagerGlobal {
return index;
}
- public static boolean shouldDestroyEglContext(int trimLevel) {
- // On low-end gfx devices we trim when memory is moderate;
- // on high-end devices we do this when low.
- if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_COMPLETE) {
- return true;
- }
- if (trimLevel >= ComponentCallbacks2.TRIM_MEMORY_MODERATE
- && !ActivityManager.isHighEndGfx()) {
- return true;
- }
- return false;
- }
-
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
public void trimMemory(int level) {
-
- if (shouldDestroyEglContext(level)) {
- // Destroy all hardware surfaces and resources associated to
- // known windows
- synchronized (mLock) {
- for (int i = mRoots.size() - 1; i >= 0; --i) {
- mRoots.get(i).destroyHardwareResources();
- }
- }
- // Force a full memory flush
- level = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;
- }
-
ThreadedRenderer.trimMemory(level);
-
- if (ThreadedRenderer.sTrimForeground) {
- doTrimForeground();
- }
- }
-
- public static void trimForeground() {
- if (ThreadedRenderer.sTrimForeground) {
- WindowManagerGlobal wm = WindowManagerGlobal.getInstance();
- wm.doTrimForeground();
- }
- }
-
- private void doTrimForeground() {
- boolean hasVisibleWindows = false;
- synchronized (mLock) {
- for (int i = mRoots.size() - 1; i >= 0; --i) {
- final ViewRootImpl root = mRoots.get(i);
- if (root.mView != null && root.getHostVisibility() == View.VISIBLE
- && root.mAttachInfo.mThreadedRenderer != null) {
- hasVisibleWindows = true;
- } else {
- root.destroyHardwareResources();
- }
- }
- }
- if (!hasVisibleWindows) {
- ThreadedRenderer.trimMemory(
- ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
- }
}
public void dumpGfxInfo(FileDescriptor fd, String[] args) {
diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java
index f2c8355adb84..f86f51fc0022 100644
--- a/core/java/android/view/accessibility/AccessibilityEvent.java
+++ b/core/java/android/view/accessibility/AccessibilityEvent.java
@@ -24,7 +24,6 @@ import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
-import android.widget.TextView;
import com.android.internal.util.BitUtils;
@@ -688,15 +687,27 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
/**
* Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
- * It means the content is invalid or associated with an error.
- * For example, text that sets an error message, such as when input isn't in a valid format,
- * should send this event and use {@link AccessibilityNodeInfo#setError} to
- * provide more context.
+ * The source node changed its content validity returned by
+ * {@link AccessibilityNodeInfo#isContentInvalid}.
+ * The view changing content validity should call
+ * {@link AccessibilityNodeInfo#setContentInvalid} and then send this event.
*
+ * @see AccessibilityNodeInfo#isContentInvalid
+ * @see AccessibilityNodeInfo#setContentInvalid
+ */
+ public static final int CONTENT_CHANGE_TYPE_CONTENT_INVALID = 0x0000400;
+
+ /**
+ * Change type for {@link #TYPE_WINDOW_CONTENT_CHANGED} event:
+ * The source node changed its erroneous content's error message returned by
+ * {@link AccessibilityNodeInfo#getError}.
+ * The view changing erroneous content's error message should call
+ * {@link AccessibilityNodeInfo#setError} and then send this event.
+ *
+ * @see AccessibilityNodeInfo#getError
* @see AccessibilityNodeInfo#setError
- * @see TextView#setError
*/
- public static final int CONTENT_CHANGE_TYPE_INVALID = 0x0000400;
+ public static final int CONTENT_CHANGE_TYPE_ERROR = 0x0000800;
/** Change type for {@link #TYPE_SPEECH_STATE_CHANGE} event: another service is speaking. */
public static final int SPEECH_STATE_SPEAKING_START = 0x00000001;
@@ -823,7 +834,8 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
CONTENT_CHANGE_TYPE_DRAG_STARTED,
CONTENT_CHANGE_TYPE_DRAG_DROPPED,
CONTENT_CHANGE_TYPE_DRAG_CANCELLED,
- CONTENT_CHANGE_TYPE_INVALID,
+ CONTENT_CHANGE_TYPE_CONTENT_INVALID,
+ CONTENT_CHANGE_TYPE_ERROR,
})
public @interface ContentChangeTypes {}
@@ -1090,7 +1102,9 @@ public final class AccessibilityEvent extends AccessibilityRecord implements Par
case CONTENT_CHANGE_TYPE_DRAG_STARTED: return "CONTENT_CHANGE_TYPE_DRAG_STARTED";
case CONTENT_CHANGE_TYPE_DRAG_DROPPED: return "CONTENT_CHANGE_TYPE_DRAG_DROPPED";
case CONTENT_CHANGE_TYPE_DRAG_CANCELLED: return "CONTENT_CHANGE_TYPE_DRAG_CANCELLED";
- case CONTENT_CHANGE_TYPE_INVALID: return "CONTENT_CHANGE_TYPE_INVALID";
+ case CONTENT_CHANGE_TYPE_CONTENT_INVALID:
+ return "CONTENT_CHANGE_TYPE_CONTENT_INVALID";
+ case CONTENT_CHANGE_TYPE_ERROR: return "CONTENT_CHANGE_TYPE_ERROR";
default: return Integer.toHexString(type);
}
}
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 90384b520315..c32ca9e2e215 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -528,7 +528,12 @@ public final class MainContentCaptureSession extends ContentCaptureSession {
@Override
@UiThread
void flush(@FlushReason int reason) {
- if (mEvents == null) return;
+ if (mEvents == null || mEvents.size() == 0) {
+ if (sVerbose) {
+ Log.v(TAG, "Don't flush for empty event buffer.");
+ }
+ return;
+ }
if (mDisabled.get()) {
Log.e(TAG, "handleForceFlush(" + getDebugState(reason) + "): should not be when "
diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java
index 6bf2474beb17..514df59f1989 100644
--- a/core/java/android/view/translation/UiTranslationController.java
+++ b/core/java/android/view/translation/UiTranslationController.java
@@ -175,10 +175,7 @@ public class UiTranslationController implements Dumpable {
*/
public void onActivityDestroyed() {
synchronized (mLock) {
- if (DEBUG) {
- Log.i(TAG,
- "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState));
- }
+ Log.i(TAG, "onActivityDestroyed(): mCurrentState is " + stateToString(mCurrentState));
if (mCurrentState != STATE_UI_TRANSLATION_FINISHED) {
notifyTranslationFinished(/* activityDestroyed= */ true);
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 752d02c59fc0..d11fa5f01ac1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7650,7 +7650,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
createEditorIfNeeded();
mEditor.setError(error, icon);
notifyViewAccessibilityStateChangedIfNeeded(
- AccessibilityEvent.CONTENT_CHANGE_TYPE_INVALID);
+ AccessibilityEvent.CONTENT_CHANGE_TYPE_ERROR
+ | AccessibilityEvent.CONTENT_CHANGE_TYPE_CONTENT_INVALID);
}
@Override
@@ -12221,6 +12222,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ @Override
+ public boolean isAutoHandwritingEnabled() {
+ return super.isAutoHandwritingEnabled() && !isAnyPasswordInputType();
+ }
+
/** @hide */
@Override
public boolean isStylusHandwritingAvailable() {
diff --git a/core/java/android/window/ScreenCapture.aidl b/core/java/android/window/ScreenCapture.aidl
new file mode 100644
index 000000000000..267a7c60b60d
--- /dev/null
+++ b/core/java/android/window/ScreenCapture.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.window;
+
+/** @hide */
+parcelable ScreenCapture.CaptureArgs;
+
+/** @hide */
+parcelable ScreenCapture.ScreenshotHardwareBuffer;
+
+/** @hide */
+parcelable ScreenCapture.ScreenCaptureListener; \ No newline at end of file
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 887d027d26a8..8a7efb93d961 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -24,11 +24,17 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.util.Log;
+import android.util.Pair;
import android.view.SurfaceControl;
+import libcore.util.NativeAllocationRegistry;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/**
* Handles display and layer captures for the system.
@@ -37,20 +43,26 @@ import java.util.concurrent.TimeUnit;
*/
public class ScreenCapture {
private static final String TAG = "ScreenCapture";
+ private static final int SCREENSHOT_WAIT_TIME_S = 1;
private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
- ScreenCaptureListener captureListener);
+ long captureListener);
private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
- ScreenCaptureListener captureListener);
+ long captureListener);
+ private static native long nativeCreateScreenCaptureListener(
+ Consumer<ScreenshotHardwareBuffer> consumer);
+ private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
+ private static native long nativeReadListenerFromParcel(Parcel in);
+ private static native long getNativeListenerFinalizer();
/**
- * @param captureArgs Arguments about how to take the screenshot
+ * @param captureArgs Arguments about how to take the screenshot
* @param captureListener A listener to receive the screenshot callback
* @hide
*/
public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
@NonNull ScreenCaptureListener captureListener) {
- return nativeCaptureDisplay(captureArgs, captureListener);
+ return nativeCaptureDisplay(captureArgs, captureListener.mNativeObject);
}
/**
@@ -61,28 +73,29 @@ public class ScreenCapture {
*/
public static ScreenshotHardwareBuffer captureDisplay(
DisplayCaptureArgs captureArgs) {
- SyncScreenCaptureListener
- screenCaptureListener = new SyncScreenCaptureListener();
-
- int status = captureDisplay(captureArgs, screenCaptureListener);
+ Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture = createSyncCaptureListener();
+ int status = captureDisplay(captureArgs, syncScreenCapture.first);
if (status != 0) {
return null;
}
- return screenCaptureListener.waitForScreenshot();
+ try {
+ return syncScreenCapture.second.get();
+ } catch (Exception e) {
+ return null;
+ }
}
/**
* Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
*
- * @param layer The root layer to capture.
- * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
- * Rect()' or null if no cropping is desired. If the root layer does not
- * have a buffer or a crop set, then a non-empty source crop must be
- * specified.
- * @param frameScale The desired scale of the returned buffer; the raw
- * screen will be scaled up/down.
- *
+ * @param layer The root layer to capture.
+ * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+ * Rect()' or null if no cropping is desired. If the root layer does not
+ * have a buffer or a crop set, then a non-empty source crop must be
+ * specified.
+ * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
+ * up/down.
* @return Returns a HardwareBuffer that contains the layer capture.
* @hide
*/
@@ -94,15 +107,14 @@ public class ScreenCapture {
/**
* Captures a layer and its children and returns a {@link HardwareBuffer} with the content.
*
- * @param layer The root layer to capture.
- * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
- * Rect()' or null if no cropping is desired. If the root layer does not
- * have a buffer or a crop set, then a non-empty source crop must be
- * specified.
- * @param frameScale The desired scale of the returned buffer; the raw
- * screen will be scaled up/down.
- * @param format The desired pixel format of the returned buffer.
- *
+ * @param layer The root layer to capture.
+ * @param sourceCrop The portion of the root surface to capture; caller may pass in 'new
+ * Rect()' or null if no cropping is desired. If the root layer does not
+ * have a buffer or a crop set, then a non-empty source crop must be
+ * specified.
+ * @param frameScale The desired scale of the returned buffer; the raw screen will be scaled
+ * up/down.
+ * @param format The desired pixel format of the returned buffer.
* @return Returns a HardwareBuffer that contains the layer capture.
* @hide
*/
@@ -120,21 +132,24 @@ public class ScreenCapture {
/**
* @hide
*/
- public static ScreenshotHardwareBuffer captureLayers(
- LayerCaptureArgs captureArgs) {
- SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();
-
- int status = captureLayers(captureArgs, screenCaptureListener);
+ public static ScreenshotHardwareBuffer captureLayers(LayerCaptureArgs captureArgs) {
+ Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture = createSyncCaptureListener();
+ int status = captureLayers(captureArgs, syncScreenCapture.first);
if (status != 0) {
return null;
}
- return screenCaptureListener.waitForScreenshot();
+ try {
+ return syncScreenCapture.second.get();
+ } catch (Exception e) {
+ return null;
+ }
}
/**
* Like {@link #captureLayers(SurfaceControl, Rect, float, int)} but with an array of layer
* handles to exclude.
+ *
* @hide
*/
public static ScreenshotHardwareBuffer captureLayersExcluding(SurfaceControl layer,
@@ -150,24 +165,13 @@ public class ScreenCapture {
}
/**
- * @param captureArgs Arguments about how to take the screenshot
+ * @param captureArgs Arguments about how to take the screenshot
* @param captureListener A listener to receive the screenshot callback
* @hide
*/
public static int captureLayers(@NonNull LayerCaptureArgs captureArgs,
@NonNull ScreenCaptureListener captureListener) {
- return nativeCaptureLayers(captureArgs, captureListener);
- }
-
- /**
- * @hide
- */
- public interface ScreenCaptureListener {
- /**
- * The callback invoked when the screen capture is complete.
- * @param hardwareBuffer Data containing info about the screen capture.
- */
- void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer);
+ return nativeCaptureLayers(captureArgs, captureListener.mNativeObject);
}
/**
@@ -190,15 +194,16 @@ public class ScreenCapture {
mContainsHdrLayers = containsHdrLayers;
}
- /**
- * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
- * @param hardwareBuffer The existing HardwareBuffer object
- * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
- * @param containsSecureLayers Indicates whether this graphic buffer contains captured
- * contents of secure layers, in which case the screenshot
- * should not be persisted.
- * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
- */
+ /**
+ * Create ScreenshotHardwareBuffer from an existing HardwareBuffer object.
+ *
+ * @param hardwareBuffer The existing HardwareBuffer object
+ * @param namedColorSpace Integer value of a named color space {@link ColorSpace.Named}
+ * @param containsSecureLayers Indicates whether this graphic buffer contains captured
+ * contents of secure layers, in which case the screenshot
+ * should not be persisted.
+ * @param containsHdrLayers Indicates whether this graphic buffer contains HDR content.
+ */
private static ScreenshotHardwareBuffer createFromNative(HardwareBuffer hardwareBuffer,
int namedColorSpace, boolean containsSecureLayers, boolean containsHdrLayers) {
ColorSpace colorSpace = ColorSpace.get(ColorSpace.Named.values()[namedColorSpace]);
@@ -220,6 +225,7 @@ public class ScreenCapture {
public boolean containsSecureLayers() {
return mContainsSecureLayers;
}
+
/**
* Returns whether the screenshot contains at least one HDR layer.
* This information may be useful for informing the display whether this screenshot
@@ -234,7 +240,7 @@ public class ScreenCapture {
* Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap
* into
* a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
- *
+ * <p>
* CAVEAT: This can be extremely slow; avoid use unless absolutely necessary; prefer to
* directly
* use the {@link HardwareBuffer} directly.
@@ -250,44 +256,23 @@ public class ScreenCapture {
}
}
- private static class SyncScreenCaptureListener implements ScreenCaptureListener {
- private static final int SCREENSHOT_WAIT_TIME_S = 1;
- private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
- private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
-
- @Override
- public void onScreenCaptureComplete(ScreenshotHardwareBuffer hardwareBuffer) {
- mScreenshotHardwareBuffer = hardwareBuffer;
- mCountDownLatch.countDown();
- }
-
- private ScreenshotHardwareBuffer waitForScreenshot() {
- try {
- mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
- } catch (Exception e) {
- Log.e(TAG, "Failed to wait for screen capture result", e);
- }
-
- return mScreenshotHardwareBuffer;
- }
- }
-
/**
* A common arguments class used for various screenshot requests. This contains arguments that
* are shared between {@link DisplayCaptureArgs} and {@link LayerCaptureArgs}
+ *
* @hide
*/
- private abstract static class CaptureArgs {
- private final int mPixelFormat;
- private final Rect mSourceCrop = new Rect();
- private final float mFrameScaleX;
- private final float mFrameScaleY;
- private final boolean mCaptureSecureLayers;
- private final boolean mAllowProtected;
- private final long mUid;
- private final boolean mGrayscale;
-
- private CaptureArgs(Builder<? extends Builder<?>> builder) {
+ public static class CaptureArgs implements Parcelable {
+ public final int mPixelFormat;
+ public final Rect mSourceCrop = new Rect();
+ public final float mFrameScaleX;
+ public final float mFrameScaleY;
+ public final boolean mCaptureSecureLayers;
+ public final boolean mAllowProtected;
+ public final long mUid;
+ public final boolean mGrayscale;
+
+ private CaptureArgs(CaptureArgs.Builder<? extends CaptureArgs.Builder<?>> builder) {
mPixelFormat = builder.mPixelFormat;
mSourceCrop.set(builder.mSourceCrop);
mFrameScaleX = builder.mFrameScaleX;
@@ -298,12 +283,23 @@ public class ScreenCapture {
mGrayscale = builder.mGrayscale;
}
+ private CaptureArgs(Parcel in) {
+ mPixelFormat = in.readInt();
+ mSourceCrop.readFromParcel(in);
+ mFrameScaleX = in.readFloat();
+ mFrameScaleY = in.readFloat();
+ mCaptureSecureLayers = in.readBoolean();
+ mAllowProtected = in.readBoolean();
+ mUid = in.readLong();
+ mGrayscale = in.readBoolean();
+ }
+
/**
* The Builder class used to construct {@link CaptureArgs}
*
- * @param <T> A builder that extends {@link Builder}
+ * @param <T> A builder that extends {@link CaptureArgs.Builder}
*/
- abstract static class Builder<T extends Builder<T>> {
+ public static class Builder<T extends CaptureArgs.Builder<T>> {
private int mPixelFormat = PixelFormat.RGBA_8888;
private final Rect mSourceCrop = new Rect();
private float mFrameScaleX = 1;
@@ -314,6 +310,14 @@ public class ScreenCapture {
private boolean mGrayscale;
/**
+ * Construct a new {@link CaptureArgs} with the set parameters. The builder remains
+ * valid.
+ */
+ public CaptureArgs build() {
+ return new CaptureArgs(this);
+ }
+
+ /**
* The desired pixel format of the returned buffer.
*/
public T setPixelFormat(int pixelFormat) {
@@ -395,15 +399,47 @@ public class ScreenCapture {
/**
* Each sub class should return itself to allow the builder to chain properly
*/
- abstract T getThis();
+ T getThis() {
+ return (T) this;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
}
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mPixelFormat);
+ mSourceCrop.writeToParcel(dest, flags);
+ dest.writeFloat(mFrameScaleX);
+ dest.writeFloat(mFrameScaleY);
+ dest.writeBoolean(mCaptureSecureLayers);
+ dest.writeBoolean(mAllowProtected);
+ dest.writeLong(mUid);
+ dest.writeBoolean(mGrayscale);
+ }
+
+ public static final Parcelable.Creator<CaptureArgs> CREATOR =
+ new Parcelable.Creator<CaptureArgs>() {
+ @Override
+ public CaptureArgs createFromParcel(Parcel in) {
+ return new CaptureArgs(in);
+ }
+
+ @Override
+ public CaptureArgs[] newArray(int size) {
+ return new CaptureArgs[size];
+ }
+ };
}
/**
* The arguments class used to make display capture requests.
*
- * @see #nativeCaptureDisplay(DisplayCaptureArgs, ScreenCaptureListener)
* @hide
+ * @see #nativeCaptureDisplay(DisplayCaptureArgs, long)
*/
public static class DisplayCaptureArgs extends CaptureArgs {
private final IBinder mDisplayToken;
@@ -488,8 +524,8 @@ public class ScreenCapture {
/**
* The arguments class used to make layer capture requests.
*
- * @see #nativeCaptureLayers(LayerCaptureArgs, ScreenCaptureListener)
* @hide
+ * @see #nativeCaptureLayers(LayerCaptureArgs, long)
*/
public static class LayerCaptureArgs extends CaptureArgs {
private final long mNativeLayer;
@@ -530,6 +566,17 @@ public class ScreenCapture {
return new LayerCaptureArgs(this);
}
+ public Builder(SurfaceControl layer, CaptureArgs args) {
+ setLayer(layer);
+ setPixelFormat(args.mPixelFormat);
+ setSourceCrop(args.mSourceCrop);
+ setFrameScale(args.mFrameScaleX, args.mFrameScaleY);
+ setCaptureSecureLayers(args.mCaptureSecureLayers);
+ setAllowProtected(args.mAllowProtected);
+ setUid(args.mUid);
+ setGrayscale(args.mGrayscale);
+ }
+
public Builder(SurfaceControl layer) {
setLayer(layer);
}
@@ -542,7 +589,6 @@ public class ScreenCapture {
return this;
}
-
/**
* An array of layer handles to exclude.
*/
@@ -564,8 +610,109 @@ public class ScreenCapture {
Builder getThis() {
return this;
}
+ }
+ }
+
+ /**
+ * The object used to receive the results when invoking screen capture requests via
+ * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)} or
+ * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)}
+ *
+ * This listener can only be used for a single call to capture content call.
+ */
+ public static class ScreenCaptureListener implements Parcelable {
+ private final long mNativeObject;
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ ScreenCaptureListener.class.getClassLoader(), getNativeListenerFinalizer());
+ /**
+ * @param consumer The callback invoked when the screen capture is complete.
+ */
+ public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
+ mNativeObject = nativeCreateScreenCaptureListener(consumer);
+ sRegistry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ private ScreenCaptureListener(Parcel in) {
+ if (in.readBoolean()) {
+ mNativeObject = nativeReadListenerFromParcel(in);
+ sRegistry.registerNativeAllocation(this, mNativeObject);
+ } else {
+ mNativeObject = 0;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ if (mNativeObject == 0) {
+ dest.writeBoolean(false);
+ } else {
+ dest.writeBoolean(true);
+ nativeWriteListenerToParcel(mNativeObject, dest);
+ }
}
+
+ public static final Parcelable.Creator<ScreenCaptureListener> CREATOR =
+ new Parcelable.Creator<ScreenCaptureListener>() {
+ @Override
+ public ScreenCaptureListener createFromParcel(Parcel in) {
+ return new ScreenCaptureListener(in);
+ }
+
+ @Override
+ public ScreenCaptureListener[] newArray(int size) {
+ return new ScreenCaptureListener[0];
+ }
+ };
+ }
+
+ /**
+ * A helper method to handle the async screencapture callbacks synchronously. This should only
+ * be used if the screencapture caller doesn't care that it blocks waiting for a screenshot.
+ *
+ * @return a Pair that holds the {@link ScreenCaptureListener} that should be used for capture
+ * calls into SurfaceFlinger and a {@link ScreenshotSync} object to retrieve the results.
+ */
+ public static Pair<ScreenCaptureListener, ScreenshotSync> createSyncCaptureListener() {
+ final ScreenshotSync screenshotSync = new ScreenshotSync();
+ final ScreenCaptureListener screenCaptureListener = new ScreenCaptureListener(
+ screenshotSync::setScreenshotHardwareBuffer);
+ return new Pair<>(screenCaptureListener, screenshotSync);
}
+ /**
+ * Helper class to synchronously get the {@link ScreenshotHardwareBuffer} when calling
+ * {@link #captureLayers(LayerCaptureArgs, ScreenCaptureListener)} or
+ * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)}
+ */
+ public static class ScreenshotSync {
+ private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+ private ScreenshotHardwareBuffer mScreenshotHardwareBuffer;
+
+ private void setScreenshotHardwareBuffer(
+ ScreenshotHardwareBuffer screenshotHardwareBuffer) {
+ mScreenshotHardwareBuffer = screenshotHardwareBuffer;
+ mCountDownLatch.countDown();
+ }
+
+ /**
+ * Get the {@link ScreenshotHardwareBuffer} synchronously. This can be null if the
+ * screenshot failed or if there was no callback in {@link #SCREENSHOT_WAIT_TIME_S} seconds.
+ */
+ public ScreenshotHardwareBuffer get() {
+ try {
+ mCountDownLatch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+ return mScreenshotHardwareBuffer;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to wait for screen capture result", e);
+ return null;
+ }
+ }
+ }
}
diff --git a/core/java/com/android/internal/jank/EventLogTags.logtags b/core/java/com/android/internal/jank/EventLogTags.logtags
new file mode 100644
index 000000000000..6139bce04fb3
--- /dev/null
+++ b/core/java/com/android/internal/jank/EventLogTags.logtags
@@ -0,0 +1,10 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.internal.jank;
+
+# Marks a request to start tracing a CUJ. Doesn't mean the request was executed.
+37001 jank_cuj_events_begin_request (CUJ Type|1|5)
+# Marks a request to end tracing a CUJ. Doesn't mean the request was executed.
+37002 jank_cuj_events_end_request (CUJ Type|1|5)
+# Marks a request to cancel tracing a CUJ. Doesn't mean the request was executed.
+37003 jank_cuj_events_cancel_request (CUJ Type|1|5)
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 40d192ea6827..76f33a6c3937 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -541,6 +541,7 @@ public class InteractionJankMonitor {
public boolean begin(@NonNull Configuration.Builder builder) {
try {
final Configuration config = builder.build();
+ EventLogTags.writeJankCujEventsBeginRequest(config.mCujType);
final TrackerResult result = new TrackerResult();
final boolean success = config.getHandler().runWithScissors(
() -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT);
@@ -614,6 +615,7 @@ public class InteractionJankMonitor {
* @return boolean true if the tracker is ended successfully, false otherwise.
*/
public boolean end(@CujType int cujType) {
+ EventLogTags.writeJankCujEventsEndRequest(cujType);
FrameTracker tracker = getTracker(cujType);
// Skip this call since we haven't started a trace yet.
if (tracker == null) return false;
@@ -651,6 +653,7 @@ public class InteractionJankMonitor {
* @return boolean true if the tracker is cancelled successfully, false otherwise.
*/
public boolean cancel(@CujType int cujType) {
+ EventLogTags.writeJankCujEventsCancelRequest(cujType);
return cancel(cujType, REASON_CANCEL_NORMAL);
}
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
index f073c1c046c5..2bfde242a987 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambda.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
@@ -22,36 +22,24 @@ import static com.android.internal.util.function.pooled.PooledLambdaImpl.acquire
import android.os.Message;
import com.android.internal.util.function.DecConsumer;
-import com.android.internal.util.function.DecFunction;
import com.android.internal.util.function.DodecConsumer;
-import com.android.internal.util.function.DodecFunction;
import com.android.internal.util.function.HeptConsumer;
-import com.android.internal.util.function.HeptFunction;
import com.android.internal.util.function.HexConsumer;
-import com.android.internal.util.function.HexFunction;
import com.android.internal.util.function.NonaConsumer;
-import com.android.internal.util.function.NonaFunction;
import com.android.internal.util.function.OctConsumer;
-import com.android.internal.util.function.OctFunction;
import com.android.internal.util.function.QuadConsumer;
-import com.android.internal.util.function.QuadFunction;
import com.android.internal.util.function.QuadPredicate;
import com.android.internal.util.function.QuintConsumer;
-import com.android.internal.util.function.QuintFunction;
import com.android.internal.util.function.QuintPredicate;
import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.TriFunction;
import com.android.internal.util.function.TriPredicate;
import com.android.internal.util.function.UndecConsumer;
-import com.android.internal.util.function.UndecFunction;
import com.android.internal.util.function.pooled.PooledLambdaImpl.LambdaType.ReturnType;
import java.util.function.BiConsumer;
-import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
-import java.util.function.Predicate;
import java.util.function.Supplier;
/**
@@ -194,40 +182,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1) }
- */
- static <A> PooledSupplier<Boolean> obtainSupplier(
- Predicate<? super A> function,
- A arg1) {
- return acquire(PooledLambdaImpl.sPool,
- function, 1, 0, ReturnType.BOOLEAN, arg1, null, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1) }
- */
- static <A, R> PooledSupplier<R> obtainSupplier(
- Function<? super A, ? extends R> function,
- A arg1) {
- return acquire(PooledLambdaImpl.sPool,
- function, 1, 0, ReturnType.OBJECT, arg1, null, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -279,42 +233,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2) }
- */
- static <A, B> PooledSupplier<Boolean> obtainSupplier(
- BiPredicate<? super A, ? super B> function,
- A arg1, B arg2) {
- return acquire(PooledLambdaImpl.sPool,
- function, 2, 0, ReturnType.BOOLEAN, arg1, arg2, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2) }
- */
- static <A, B, R> PooledSupplier<R> obtainSupplier(
- BiFunction<? super A, ? super B, ? extends R> function,
- A arg1, B arg2) {
- return acquire(PooledLambdaImpl.sPool,
- function, 2, 0, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -411,24 +329,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg2 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg1) -> function(arg1, arg2) }
- */
- static <A, B, R> PooledFunction<A, R> obtainFunction(
- BiFunction<? super A, ? super B, ? extends R> function,
- ArgumentPlaceholder<A> arg1, B arg2) {
- return acquire(PooledLambdaImpl.sPool,
- function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -465,24 +365,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg2) -> function(arg1, arg2) }
- */
- static <A, B, R> PooledFunction<B, R> obtainFunction(
- BiFunction<? super A, ? super B, ? extends R> function,
- A arg1, ArgumentPlaceholder<B> arg2) {
- return acquire(PooledLambdaImpl.sPool,
- function, 2, 1, ReturnType.OBJECT, arg1, arg2, null, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -536,25 +418,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3) }
- */
- static <A, B, C, R> PooledSupplier<R> obtainSupplier(
- TriFunction<? super A, ? super B, ? super C, ? extends R> function,
- A arg1, B arg2, C arg3) {
- return acquire(PooledLambdaImpl.sPool,
- function, 3, 0, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -574,25 +437,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg1) -> function(arg1, arg2, arg3) }
- */
- static <A, B, C, R> PooledFunction<A, R> obtainFunction(
- TriFunction<? super A, ? super B, ? super C, ? extends R> function,
- ArgumentPlaceholder<A> arg1, B arg2, C arg3) {
- return acquire(PooledLambdaImpl.sPool,
- function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -612,25 +456,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg3 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg2) -> function(arg1, arg2, arg3) }
- */
- static <A, B, C, R> PooledFunction<B, R> obtainFunction(
- TriFunction<? super A, ? super B, ? super C, ? extends R> function,
- A arg1, ArgumentPlaceholder<B> arg2, C arg3) {
- return acquire(PooledLambdaImpl.sPool,
- function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -650,25 +475,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 placeholder for a missing argument. Use {@link #__} to get one
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg3) -> function(arg1, arg2, arg3) }
- */
- static <A, B, C, R> PooledFunction<C, R> obtainFunction(
- TriFunction<? super A, ? super B, ? super C, ? extends R> function,
- A arg1, B arg2, ArgumentPlaceholder<C> arg3) {
- return acquire(PooledLambdaImpl.sPool,
- function, 3, 1, ReturnType.OBJECT, arg1, arg2, arg3, null, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -724,26 +530,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledSupplier<R> obtainSupplier(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -764,26 +550,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg1) -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledFunction<A, R> obtainFunction(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- ArgumentPlaceholder<A> arg1, B arg2, C arg3, D arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -804,26 +570,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg2) -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledFunction<B, R> obtainFunction(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- A arg1, ArgumentPlaceholder<B> arg2, C arg3, D arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -844,26 +590,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 placeholder for a missing argument. Use {@link #__} to get one
- * @param arg4 parameter supplied to {@code function} on call
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg3) -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledFunction<C, R> obtainFunction(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- A arg1, B arg2, ArgumentPlaceholder<C> arg3, D arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* {@link PooledConsumer} factory
*
* @param function non-capturing lambda(typically an unbounded method reference)
@@ -884,26 +610,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledFunction} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 placeholder for a missing argument. Use {@link #__} to get one
- * @return a {@link PooledFunction}, equivalent to lambda:
- * {@code (arg4) -> function(arg1, arg2, arg3, arg4) }
- */
- static <A, B, C, D, R> PooledFunction<D, R> obtainFunction(
- QuadFunction<? super A, ? super B, ? super C, ? super D, ? extends R> function,
- A arg1, B arg2, C arg3, ArgumentPlaceholder<D> arg4) {
- return acquire(PooledLambdaImpl.sPool,
- function, 4, 1, ReturnType.OBJECT, arg1, arg2, arg3, arg4, null, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -961,27 +667,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5) }
- */
- static <A, B, C, D, E, R> PooledSupplier<R> obtainSupplier(
- QuintFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? extends R>
- function, A arg1, B arg2, C arg3, D arg4, E arg5) {
- return acquire(PooledLambdaImpl.sPool,
- function, 5, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, null, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1042,28 +727,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6) }
- */
- static <A, B, C, D, E, F, R> PooledSupplier<R> obtainSupplier(
- HexFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? extends R> function, A arg1, B arg2, C arg3, D arg4, E arg5, F arg6) {
- return acquire(PooledLambdaImpl.sPool,
- function, 6, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, null, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1126,30 +789,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7) }
- */
- static <A, B, C, D, E, F, G, R> PooledSupplier<R> obtainSupplier(
- HeptFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7) {
- return acquire(PooledLambdaImpl.sPool,
- function, 7, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, null,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1215,31 +854,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) }
- */
- static <A, B, C, D, E, F, G, H, R> PooledSupplier<R> obtainSupplier(
- OctFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8) {
- return acquire(PooledLambdaImpl.sPool,
- function, 8, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- null, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1308,32 +922,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @param arg9 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) }
- */
- static <A, B, C, D, E, F, G, H, I, R> PooledSupplier<R> obtainSupplier(
- NonaFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? super I, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9) {
- return acquire(PooledLambdaImpl.sPool,
- function, 9, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- arg9, null, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1404,33 +992,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @param arg9 parameter supplied to {@code function} on call
- * @param arg10 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) }
- */
- static <A, B, C, D, E, F, G, H, I, J, R> PooledSupplier<R> obtainSupplier(
- DecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? super I, ? super J, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10) {
- return acquire(PooledLambdaImpl.sPool,
- function, 10, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- arg9, arg10, null, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1504,36 +1065,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @param arg9 parameter supplied to {@code function} on call
- * @param arg10 parameter supplied to {@code function} on call
- * @param arg11 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,
- * arg11) }
- */
- static <A, B, C, D, E, F, G, H, I, J, K, R> PooledSupplier<R> obtainSupplier(
- UndecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? super I, ? super J, ? super K, ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10,
- K arg11) {
- return acquire(PooledLambdaImpl.sPool,
- function, 11, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- arg9, arg10, arg11, null);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
@@ -1611,38 +1142,6 @@ public interface PooledLambda {
}
/**
- * {@link PooledSupplier} factory
- *
- * @param function non-capturing lambda(typically an unbounded method reference)
- * to be invoked on call
- * @param arg1 parameter supplied to {@code function} on call
- * @param arg2 parameter supplied to {@code function} on call
- * @param arg3 parameter supplied to {@code function} on call
- * @param arg4 parameter supplied to {@code function} on call
- * @param arg5 parameter supplied to {@code function} on call
- * @param arg6 parameter supplied to {@code function} on call
- * @param arg7 parameter supplied to {@code function} on call
- * @param arg8 parameter supplied to {@code function} on call
- * @param arg9 parameter supplied to {@code function} on call
- * @param arg10 parameter supplied to {@code function} on call
- * @param arg11 parameter supplied to {@code function} on call
- * @param arg12 parameter supplied to {@code function} on call
- * @return a {@link PooledSupplier}, equivalent to lambda:
- * {@code () -> function(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10,
- * arg11) }
- */
- static <A, B, C, D, E, F, G, H, I, J, K, L, R> PooledSupplier<R> obtainSupplier(
- DodecFunction<? super A, ? super B, ? super C, ? super D, ? super E, ? super F,
- ? super G, ? super H, ? super I, ? super J, ? super K, ? extends L,
- ? extends R> function,
- A arg1, B arg2, C arg3, D arg4, E arg5, F arg6, G arg7, H arg8, I arg9, J arg10,
- K arg11, L arg12) {
- return acquire(PooledLambdaImpl.sPool,
- function, 11, 0, ReturnType.OBJECT, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8,
- arg9, arg10, arg11, arg12);
- }
-
- /**
* Factory of {@link Message}s that contain an
* ({@link PooledLambda#recycleOnUse auto-recycling}) {@link PooledRunnable} as its
* {@link Message#getCallback internal callback}.
diff --git a/core/jni/android_media_AudioProductStrategies.cpp b/core/jni/android_media_AudioProductStrategies.cpp
index 34be2a52344d..4b563d7f3bd8 100644
--- a/core/jni/android_media_AudioProductStrategies.cpp
+++ b/core/jni/android_media_AudioProductStrategies.cpp
@@ -86,8 +86,8 @@ static jint convertAudioProductStrategiesFromNative(
// Audio Attributes Group array
int attrGroupIndex = 0;
- std::map<int /**attributesGroupIndex*/, std::vector<AudioAttributes> > groups;
- for (const auto &attr : strategy.getAudioAttributes()) {
+ std::map<int /**attributesGroupIndex*/, std::vector<VolumeGroupAttributes> > groups;
+ for (const auto &attr : strategy.getVolumeGroupAttributes()) {
int groupId = attr.getGroupId();
int streamType = attr.getStreamType();
const auto &iter = std::find_if(begin(groups), end(groups),
@@ -108,17 +108,17 @@ static jint convertAudioProductStrategiesFromNative(
jAudioAttributesGroups = env->NewObjectArray(numAttributesGroups, gAudioAttributesGroupClass, NULL);
for (const auto &iter : groups) {
- std::vector<AudioAttributes> audioAttributesGroups = iter.second;
- jint numAttributes = audioAttributesGroups.size();
- jint jGroupId = audioAttributesGroups.front().getGroupId();
- jint jLegacyStreamType = audioAttributesGroups.front().getStreamType();
+ std::vector<VolumeGroupAttributes> volumeGroupAttributes = iter.second;
+ jint numAttributes = volumeGroupAttributes.size();
+ jint jGroupId = volumeGroupAttributes.front().getGroupId();
+ jint jLegacyStreamType = volumeGroupAttributes.front().getStreamType();
jStatus = JNIAudioAttributeHelper::getJavaArray(env, &jAudioAttributes, numAttributes);
if (jStatus != (jint)AUDIO_JAVA_SUCCESS) {
goto exit;
}
for (size_t j = 0; j < static_cast<size_t>(numAttributes); j++) {
- auto attributes = audioAttributesGroups[j].getAttributes();
+ auto attributes = volumeGroupAttributes[j].getAttributes();
jStatus = JNIAudioAttributeHelper::nativeToJava(env, &jAudioAttribute, attributes);
if (jStatus != AUDIO_JAVA_SUCCESS) {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b5a78b0fc46b..b60ec9f493c2 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -631,7 +631,7 @@ static jint NativeGetResourceValue(JNIEnv* env, jclass /*clazz*/, jlong ptr, jin
jshort density, jobject typed_value,
jboolean resolve_references) {
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
- ResourceTimer _tag(ResourceTimer::Counter::GetResourceValue);
+ ResourceTimer _timer(ResourceTimer::Counter::GetResourceValue);
auto value = assetmanager->GetResource(static_cast<uint32_t>(resid), false /*may_be_bag*/,
static_cast<uint16_t>(density));
if (!value.has_value()) {
@@ -1234,7 +1234,7 @@ static jboolean NativeRetrieveAttributes(JNIEnv* env, jclass /*clazz*/, jlong pt
}
ScopedLock<AssetManager2> assetmanager(AssetManagerFromLong(ptr));
- ResourceTimer _tag(ResourceTimer::Counter::RetrieveAttributes);
+ ResourceTimer _timer(ResourceTimer::Counter::RetrieveAttributes);
ResXMLParser* xml_parser = reinterpret_cast<ResXMLParser*>(xml_parser_ptr);
auto result =
RetrieveAttributes(assetmanager.get(), xml_parser, reinterpret_cast<uint32_t*>(attrs),
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 3bada15aa834..c1929c6535fb 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -61,9 +61,8 @@ static struct {
} gLayerCaptureArgsClassInfo;
static struct {
- jclass clazz;
- jmethodID onScreenCaptureComplete;
-} gScreenCaptureListenerClassInfo;
+ jmethodID accept;
+} gConsumerClassInfo;
static struct {
jclass clazz;
@@ -98,14 +97,14 @@ class ScreenCaptureListenerWrapper : public gui::BnScreenCaptureListener {
public:
explicit ScreenCaptureListenerWrapper(JNIEnv* env, jobject jobject) {
env->GetJavaVM(&mVm);
- mScreenCaptureListenerObject = env->NewGlobalRef(jobject);
- LOG_ALWAYS_FATAL_IF(!mScreenCaptureListenerObject, "Failed to make global ref");
+ mConsumerObject = env->NewGlobalRef(jobject);
+ LOG_ALWAYS_FATAL_IF(!mConsumerObject, "Failed to make global ref");
}
~ScreenCaptureListenerWrapper() {
- if (mScreenCaptureListenerObject) {
- getenv()->DeleteGlobalRef(mScreenCaptureListenerObject);
- mScreenCaptureListenerObject = nullptr;
+ if (mConsumerObject) {
+ getenv()->DeleteGlobalRef(mConsumerObject);
+ mConsumerObject = nullptr;
}
}
@@ -113,9 +112,8 @@ public:
const gui::ScreenCaptureResults& captureResults) override {
JNIEnv* env = getenv();
if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
- env->CallVoidMethod(mScreenCaptureListenerObject,
- gScreenCaptureListenerClassInfo.onScreenCaptureComplete, nullptr);
- checkAndClearException(env, "onScreenCaptureComplete");
+ env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, nullptr);
+ checkAndClearException(env, "accept");
return binder::Status::ok();
}
captureResults.fenceResult.value()->waitForever(LOG_TAG);
@@ -130,17 +128,15 @@ public:
captureResults.capturedSecureLayers,
captureResults.capturedHdrLayers);
checkAndClearException(env, "builder");
- env->CallVoidMethod(mScreenCaptureListenerObject,
- gScreenCaptureListenerClassInfo.onScreenCaptureComplete,
- screenshotHardwareBuffer);
- checkAndClearException(env, "onScreenCaptureComplete");
+ env->CallVoidMethod(mConsumerObject, gConsumerClassInfo.accept, screenshotHardwareBuffer);
+ checkAndClearException(env, "accept");
env->DeleteLocalRef(jhardwareBuffer);
env->DeleteLocalRef(screenshotHardwareBuffer);
return binder::Status::ok();
}
private:
- jobject mScreenCaptureListenerObject;
+ jobject mConsumerObject;
JavaVM* mVm;
JNIEnv* getenv() {
@@ -194,7 +190,7 @@ static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
}
static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
- jobject screenCaptureListenerObject) {
+ jlong screenCaptureListenerObject) {
const DisplayCaptureArgs captureArgs =
displayCaptureArgsFromObject(env, displayCaptureArgsObject);
@@ -202,13 +198,13 @@ static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptu
return BAD_VALUE;
}
- sp<IScreenCaptureListener> captureListener =
- sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
+ sp<gui::IScreenCaptureListener> captureListener =
+ reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
return ScreenshotClient::captureDisplay(captureArgs, captureListener);
}
static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
- jobject screenCaptureListenerObject) {
+ jlong screenCaptureListenerObject) {
LayerCaptureArgs captureArgs;
getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
@@ -238,21 +234,70 @@ static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureA
}
}
- sp<IScreenCaptureListener> captureListener =
- sp<ScreenCaptureListenerWrapper>::make(env, screenCaptureListenerObject);
+ sp<gui::IScreenCaptureListener> captureListener =
+ reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
return ScreenshotClient::captureLayers(captureArgs, captureListener);
}
+static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
+ sp<gui::IScreenCaptureListener> listener =
+ sp<ScreenCaptureListenerWrapper>::make(env, consumerObj);
+ listener->incStrong((void*)nativeCreateScreenCaptureListener);
+ return reinterpret_cast<jlong>(listener.get());
+}
+
+static void nativeWriteListenerToParcel(JNIEnv* env, jclass clazz, jlong nativeObject,
+ jobject parcelObj) {
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ if (parcel == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return;
+ }
+ ScreenCaptureListenerWrapper* const self =
+ reinterpret_cast<ScreenCaptureListenerWrapper*>(nativeObject);
+ if (self != nullptr) {
+ parcel->writeStrongBinder(IInterface::asBinder(self));
+ }
+}
+
+static jlong nativeReadListenerFromParcel(JNIEnv* env, jclass clazz, jobject parcelObj) {
+ Parcel* parcel = parcelForJavaObject(env, parcelObj);
+ if (parcel == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return 0;
+ }
+ sp<gui::IScreenCaptureListener> listener =
+ interface_cast<gui::IScreenCaptureListener>(parcel->readStrongBinder());
+ if (listener == nullptr) {
+ return 0;
+ }
+ listener->incStrong((void*)nativeCreateScreenCaptureListener);
+ return reinterpret_cast<jlong>(listener.get());
+}
+
+void destroyNativeListener(void* ptr) {
+ ScreenCaptureListenerWrapper* listener = reinterpret_cast<ScreenCaptureListenerWrapper*>(ptr);
+ listener->decStrong((void*)nativeCreateScreenCaptureListener);
+}
+
+static jlong getNativeListenerFinalizer(JNIEnv* env, jclass clazz) {
+ return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeListener));
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod sScreenCaptureMethods[] = {
// clang-format off
- {"nativeCaptureDisplay",
- "(Landroid/window/ScreenCapture$DisplayCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
+ {"nativeCaptureDisplay", "(Landroid/window/ScreenCapture$DisplayCaptureArgs;J)I",
(void*)nativeCaptureDisplay },
- {"nativeCaptureLayers",
- "(Landroid/window/ScreenCapture$LayerCaptureArgs;Landroid/window/ScreenCapture$ScreenCaptureListener;)I",
+ {"nativeCaptureLayers", "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
(void*)nativeCaptureLayers },
+ {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J",
+ (void*)nativeCreateScreenCaptureListener },
+ {"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
+ {"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
+ (void*)nativeReadListenerFromParcel },
+ {"getNativeListenerFinalizer", "()J", (void*)getNativeListenerFinalizer },
// clang-format on
};
@@ -293,12 +338,8 @@ int register_android_window_ScreenCapture(JNIEnv* env) {
gLayerCaptureArgsClassInfo.childrenOnly =
GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
- jclass screenCaptureListenerClazz =
- FindClassOrDie(env, "android/window/ScreenCapture$ScreenCaptureListener");
- gScreenCaptureListenerClassInfo.clazz = MakeGlobalRefOrDie(env, screenCaptureListenerClazz);
- gScreenCaptureListenerClassInfo.onScreenCaptureComplete =
- GetMethodIDOrDie(env, screenCaptureListenerClazz, "onScreenCaptureComplete",
- "(Landroid/window/ScreenCapture$ScreenshotHardwareBuffer;)V");
+ jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
+ gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
jclass screenshotGraphicsBufferClazz =
FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");
diff --git a/core/res/Android.bp b/core/res/Android.bp
index 93ce7832824b..7e17840445ab 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -130,6 +130,10 @@ android_app {
// Allow overlay to add resource
"--auto-add-overlay",
+
+ // Framework resources benefit tremendously from enabling sparse encoding, saving tens
+ // of MBs in size and RAM use.
+ "--enable-sparse-encoding",
],
resource_zips: [
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 78c07107bb2a..0b32cb72ada4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3509,7 +3509,7 @@
application-specific locale configs.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|installer" />
<!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
<p>Not for use by third-party applications. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9b060593651f..5f9911358921 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5512,7 +5512,7 @@
<string name="app_streaming_blocked_message_for_settings_dialog" product="default">This can’t be accessed on your <xliff:g id="device" example="Chromebook">%1$s</xliff:g>. Try on your phone instead.</string>
<!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
- <string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
+ <string name="deprecated_target_sdk_message">This app was built for an older version of Android. It might not work properly and doesn\'t include the latest security and privacy protections. Check for an update, or contact the app\'s developer.</string>
<!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
<string name="deprecated_target_sdk_app_store">Check for update</string>
diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
index 3ecc7ff17114..bf65af32e0ac 100644
--- a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java
@@ -131,7 +131,7 @@ public class InputDeviceLightsManagerTest {
new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
Light.LIGHT_CAPABILITY_BRIGHTNESS),
new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
- Light.LIGHT_CAPABILITY_RGB),
+ Light.LIGHT_CAPABILITY_COLOR_RGB),
new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
0 /* capabilities */)
};
@@ -150,13 +150,13 @@ public class InputDeviceLightsManagerTest {
Light[] mockedLights = {
new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
- Light.LIGHT_CAPABILITY_RGB),
+ Light.LIGHT_CAPABILITY_COLOR_RGB),
new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
- Light.LIGHT_CAPABILITY_RGB),
+ Light.LIGHT_CAPABILITY_COLOR_RGB),
new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
- Light.LIGHT_CAPABILITY_RGB),
+ Light.LIGHT_CAPABILITY_COLOR_RGB),
new Light(4 /* id */, "Light4", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
- Light.LIGHT_CAPABILITY_RGB)
+ Light.LIGHT_CAPABILITY_COLOR_RGB)
};
mockLights(mockedLights);
@@ -204,7 +204,7 @@ public class InputDeviceLightsManagerTest {
new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_PLAYER_ID,
0 /* capabilities */),
new Light(2 /* id */, "Light2", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
- Light.LIGHT_CAPABILITY_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS),
+ Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS),
new Light(3 /* id */, "Light3", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
Light.LIGHT_CAPABILITY_BRIGHTNESS)
};
@@ -239,9 +239,9 @@ public class InputDeviceLightsManagerTest {
@Test
public void testLightCapabilities() throws Exception {
Light light = new Light(1 /* id */, "Light1", 0 /* ordinal */, Light.LIGHT_TYPE_INPUT,
- Light.LIGHT_CAPABILITY_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS);
+ Light.LIGHT_CAPABILITY_COLOR_RGB | Light.LIGHT_CAPABILITY_BRIGHTNESS);
assertThat(light.getType()).isEqualTo(Light.LIGHT_TYPE_INPUT);
- assertThat(light.getCapabilities()).isEqualTo(Light.LIGHT_CAPABILITY_RGB
+ assertThat(light.getCapabilities()).isEqualTo(Light.LIGHT_CAPABILITY_COLOR_RGB
| Light.LIGHT_CAPABILITY_BRIGHTNESS);
assertTrue(light.hasBrightnessControl());
assertTrue(light.hasRgbControl());
diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java
index 32c3a268153c..91fbe0034133 100644
--- a/core/tests/coretests/src/android/os/FileUtilsTest.java
+++ b/core/tests/coretests/src/android/os/FileUtilsTest.java
@@ -532,6 +532,56 @@ public class FileUtilsTest {
}
@Test
+ public void testParseSize() {
+ assertEquals(0L, FileUtils.parseSize("0MB"));
+ assertEquals(1_024L, FileUtils.parseSize("1024b"));
+ assertEquals(-1L, FileUtils.parseSize(" -1 b "));
+ assertEquals(0L, FileUtils.parseSize(" -0 gib "));
+ assertEquals(1_000L, FileUtils.parseSize("1K"));
+ assertEquals(1_000L, FileUtils.parseSize("1KB"));
+ assertEquals(10_000L, FileUtils.parseSize("10KB"));
+ assertEquals(100_000L, FileUtils.parseSize("100KB"));
+ assertEquals(1_000_000L, FileUtils.parseSize("1000KB"));
+ assertEquals(1_024_000L, FileUtils.parseSize("1000KiB"));
+ assertEquals(70_000_000L, FileUtils.parseSize("070M"));
+ assertEquals(70_000_000L, FileUtils.parseSize("070MB"));
+ assertEquals(73_400_320L, FileUtils.parseSize("70MiB"));
+ assertEquals(700_000_000L, FileUtils.parseSize("700000KB"));
+ assertEquals(200_000_000L, FileUtils.parseSize("+200MB"));
+ assertEquals(1_000_000_000L, FileUtils.parseSize("1000MB"));
+ assertEquals(1_000_000_000L, FileUtils.parseSize("+1000 mb"));
+ assertEquals(644_245_094_400L, FileUtils.parseSize("600GiB"));
+ assertEquals(999_000_000_000L, FileUtils.parseSize("999GB"));
+ assertEquals(999_000_000_000L, FileUtils.parseSize("999 gB"));
+ assertEquals(9_999_000_000_000L, FileUtils.parseSize("9999GB"));
+ assertEquals(9_000_000_000_000L, FileUtils.parseSize(" 9000 GB "));
+ assertEquals(1_234_000_000_000L, FileUtils.parseSize(" 1234 GB "));
+ assertEquals(1_234_567_890_000L, FileUtils.parseSize(" 1234567890 KB "));
+ }
+
+ @Test
+ public void testParseSize_invalidArguments() {
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(null));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("null"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(""));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" "));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("KB"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123 dd"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("Invalid"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" ABC890 KB "));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-=+90 KB "));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("123"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("--123"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("-KB"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("++123"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+ 1 +"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("+--+ 1 +"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize("1GB+"));
+ assertEquals(Long.MIN_VALUE, FileUtils.parseSize(" + 1234567890 KB "));
+ }
+
+ @Test
public void testTranslateMode() throws Exception {
assertTranslate("r", O_RDONLY, MODE_READ_ONLY);
diff --git a/core/tests/coretests/src/android/os/ProcessTest.java b/core/tests/coretests/src/android/os/ProcessTest.java
index ae4edb97aab0..52846dfbb14b 100644
--- a/core/tests/coretests/src/android/os/ProcessTest.java
+++ b/core/tests/coretests/src/android/os/ProcessTest.java
@@ -72,4 +72,7 @@ public class ProcessTest extends TestCase {
assertEquals(-1, Process.getThreadGroupLeader(BAD_PID));
}
+ public void testGetAdvertisedMem() {
+ assertTrue(Process.getTotalMemory() <= Process.getAdvertisedMem());
+ }
}
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index ca3c84729388..31df474eb10c 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -24,7 +24,7 @@ import android.annotation.Size;
import android.annotation.SuppressAutoDoc;
import android.annotation.SuppressLint;
import android.hardware.DataSpace;
-import android.hardware.DataSpace.NamedDataSpace;
+import android.hardware.DataSpace.ColorDataSpace;
import android.util.SparseIntArray;
import libcore.util.NativeAllocationRegistry;
@@ -1406,7 +1406,7 @@ public abstract class ColorSpace {
*/
@SuppressLint("MethodNameUnits")
@Nullable
- public static ColorSpace getFromDataSpace(@NamedDataSpace int dataSpace) {
+ public static ColorSpace getFromDataSpace(@ColorDataSpace int dataSpace) {
int index = sDataToColorSpaces.get(dataSpace, -1);
if (index != -1) {
return ColorSpace.get(index);
@@ -1425,7 +1425,7 @@ public abstract class ColorSpace {
* @return the dataspace value.
*/
@SuppressLint("MethodNameUnits")
- public @NamedDataSpace int getDataSpace() {
+ public @ColorDataSpace int getDataSpace() {
int index = sDataToColorSpaces.indexOfValue(getId());
if (index != -1) {
return sDataToColorSpaces.keyAt(index);
diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java
index 0e67f1f4842a..c6731d112b93 100644
--- a/graphics/java/android/graphics/HardwareRenderer.java
+++ b/graphics/java/android/graphics/HardwareRenderer.java
@@ -47,9 +47,7 @@ import java.io.File;
import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.util.Optional;
import java.util.concurrent.Executor;
-import java.util.stream.Stream;
import sun.misc.Cleaner;
@@ -1142,6 +1140,16 @@ public class HardwareRenderer {
}
/**
+ * Sets whether or not the current process is a system or persistent process. Used to influence
+ * the chosen memory usage policy.
+ *
+ * @hide
+ **/
+ public static void setIsSystemOrPersistent() {
+ nSetIsSystemOrPersistent(true);
+ }
+
+ /**
* Returns true if HardwareRender will produce output.
*
* This value is global to the process and affects all uses of HardwareRenderer,
@@ -1204,30 +1212,6 @@ public class HardwareRenderer {
private static class ProcessInitializer {
static ProcessInitializer sInstance = new ProcessInitializer();
- // Magic values from android/data_space.h
- private static final int INTERNAL_DATASPACE_SRGB = 142671872;
- private static final int INTERNAL_DATASPACE_DISPLAY_P3 = 143261696;
- private static final int INTERNAL_DATASPACE_SCRGB = 411107328;
-
- private enum Dataspace {
- DISPLAY_P3(ColorSpace.Named.DISPLAY_P3, INTERNAL_DATASPACE_DISPLAY_P3),
- SCRGB(ColorSpace.Named.EXTENDED_SRGB, INTERNAL_DATASPACE_SCRGB),
- SRGB(ColorSpace.Named.SRGB, INTERNAL_DATASPACE_SRGB);
-
- private final ColorSpace.Named mColorSpace;
- private final int mNativeDataspace;
- Dataspace(ColorSpace.Named colorSpace, int nativeDataspace) {
- this.mColorSpace = colorSpace;
- this.mNativeDataspace = nativeDataspace;
- }
-
- static Optional<Dataspace> find(ColorSpace colorSpace) {
- return Stream.of(Dataspace.values())
- .filter(d -> ColorSpace.get(d.mColorSpace).equals(colorSpace))
- .findFirst();
- }
- }
-
private boolean mInitialized = false;
private boolean mDisplayInitialized = false;
@@ -1296,6 +1280,7 @@ public class HardwareRenderer {
initDisplayInfo();
nSetIsHighEndGfx(ActivityManager.isHighEndGfx());
+ nSetIsLowRam(ActivityManager.isLowRamDeviceStatic());
// Defensively clear out the context in case we were passed a context that can leak
// if we live longer than it, e.g. an activity context.
mContext = null;
@@ -1314,26 +1299,55 @@ public class HardwareRenderer {
return;
}
- Display display = dm.getDisplay(Display.DEFAULT_DISPLAY);
- if (display == null) {
+ final Display defaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
+ if (defaultDisplay == null) {
Log.d(LOG_TAG, "Failed to find default display for display-based configuration");
return;
}
- Dataspace wideColorDataspace =
- Optional.ofNullable(display.getPreferredWideGamutColorSpace())
- .flatMap(Dataspace::find)
- // Default to SRGB if the display doesn't support wide color
- .orElse(Dataspace.SRGB);
-
- // Grab the physical screen dimensions from the active display mode
- // Strictly speaking the screen resolution may not always be constant - it is for
- // sizing the font cache for the underlying rendering thread. Since it's a
- // heuristic we don't need to be always 100% correct.
- Mode activeMode = display.getMode();
- nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(),
- display.getRefreshRate(), wideColorDataspace.mNativeDataspace,
- display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos());
+ final Display[] allDisplays = dm.getDisplays();
+ if (allDisplays.length == 0) {
+ Log.d(LOG_TAG, "Failed to query displays");
+ return;
+ }
+
+ final Mode activeMode = defaultDisplay.getMode();
+ final ColorSpace defaultWideColorSpace =
+ defaultDisplay.getPreferredWideGamutColorSpace();
+ int wideColorDataspace = defaultWideColorSpace != null
+ ? defaultWideColorSpace.getDataSpace() : 0;
+ // largest width & height are used to size the default HWUI cache sizes. So find the
+ // largest display resolution we could encounter & use that as the guidance. The actual
+ // memory policy in play will interpret these values differently.
+ int largestWidth = activeMode.getPhysicalWidth();
+ int largestHeight = activeMode.getPhysicalHeight();
+
+ for (int i = 0; i < allDisplays.length; i++) {
+ final Display display = allDisplays[i];
+ // Take the first wide gamut dataspace as the source of truth
+ // Possibly should do per-HardwareRenderer wide gamut dataspace so we can use the
+ // target display's ideal instead
+ if (wideColorDataspace == 0) {
+ ColorSpace cs = display.getPreferredWideGamutColorSpace();
+ if (cs != null) {
+ wideColorDataspace = cs.getDataSpace();
+ }
+ }
+ Mode[] modes = display.getSupportedModes();
+ for (int j = 0; j < modes.length; j++) {
+ Mode mode = modes[j];
+ int width = mode.getPhysicalWidth();
+ int height = mode.getPhysicalHeight();
+ if ((width * height) > (largestWidth * largestHeight)) {
+ largestWidth = width;
+ largestHeight = height;
+ }
+ }
+ }
+
+ nInitDisplayInfo(largestWidth, largestHeight, defaultDisplay.getRefreshRate(),
+ wideColorDataspace, defaultDisplay.getAppVsyncOffsetNanos(),
+ defaultDisplay.getPresentationDeadlineNanos());
mDisplayInitialized = true;
}
@@ -1418,6 +1432,10 @@ public class HardwareRenderer {
private static native void nSetIsHighEndGfx(boolean isHighEndGfx);
+ private static native void nSetIsLowRam(boolean isLowRam);
+
+ private static native void nSetIsSystemOrPersistent(boolean isSystemOrPersistent);
+
private static native int nSyncAndDrawFrame(long nativeProxy, long[] frameInfo, int size);
private static native void nDestroy(long nativeProxy, long rootRenderNode);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index f4dda4cc461a..80cdd1f79cb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -484,12 +484,14 @@ public abstract class WMShellBaseModule {
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
+ ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
@ShellMainThread ShellExecutor mainExecutor
) {
return Optional.ofNullable(
RecentTasksController.create(context, shellInit, shellCommandHandler,
- taskStackListener, desktopModeTaskRepository, mainExecutor));
+ taskStackListener, activityTaskManager, desktopModeTaskRepository,
+ mainExecutor));
}
//
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 e784261daa7e..37a50b611039 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
@@ -188,14 +188,16 @@ public abstract class WMShellModule {
@ShellMainThread Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ @DynamicOverride DesktopModeController desktopModeController) {
return new CaptionWindowDecorViewModel(
context,
mainHandler,
mainChoreographer,
taskOrganizer,
displayController,
- syncQueue);
+ syncQueue,
+ desktopModeController);
}
//
@@ -318,6 +320,7 @@ public abstract class WMShellModule {
ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
+ PipAnimationController pipAnimationController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -337,11 +340,12 @@ public abstract class WMShellModule {
@ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(PipController.create(
context, shellInit, shellCommandHandler, shellController,
- displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
- pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
- pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, oneHandedController, mainExecutor));
+ displayController, pipAnimationController, pipAppOpsListener, pipBoundsAlgorithm,
+ pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController,
+ phonePipMenuController, pipTaskOrganizer, pipTransitionState, pipTouchHandler,
+ pipTransitionController, windowManagerShellWrapper, taskStackListener,
+ pipParamsChangedForwarder, displayInsetsController, oneHandedController,
+ mainExecutor));
}
@WMSingleton
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
index 9474cfe916f0..99739c457aa6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -177,6 +177,25 @@ public class DesktopModeController implements RemoteCallable<DesktopModeControll
}
/**
+ * Turn desktop mode on or off
+ * @param active the desired state for desktop mode setting
+ */
+ public void setDesktopModeActive(boolean active) {
+ int value = active ? 1 : 0;
+ Settings.System.putInt(mContext.getContentResolver(), Settings.System.DESKTOP_MODE, value);
+ }
+
+ /**
+ * Returns the windowing mode of the display area with the specified displayId.
+ * @param displayId
+ * @return
+ */
+ public int getDisplayAreaWindowingMode(int displayId) {
+ return mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)
+ .configuration.windowConfiguration.getWindowingMode();
+ }
+
+ /**
* A {@link ContentObserver} for listening to changes to {@link Settings.System#DESKTOP_MODE}
*/
private final class SettingsObserver extends ContentObserver {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index b32c3eed2fb4..6728c00af51b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -195,6 +195,17 @@ public class PipAnimationController {
}
/**
+ * Returns true if the PiP window is currently being animated.
+ */
+ public boolean isAnimating() {
+ PipAnimationController.PipTransitionAnimator animator = getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Quietly cancel the animator by removing the listeners first.
*/
static void quietCancel(@NonNull ValueAnimator animator) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 1a52d8c395ba..f170e774739f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -324,19 +324,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
return mPipTransitionController;
}
- /**
- * Returns true if the PiP window is currently being animated.
- */
- public boolean isAnimating() {
- // TODO(b/183746978) move this to PipAnimationController, and inject that in PipController
- PipAnimationController.PipTransitionAnimator animator =
- mPipAnimationController.getCurrentAnimator();
- if (animator != null && animator.isRunning()) {
- return true;
- }
- return false;
- }
-
public Rect getCurrentOrAnimatingBounds() {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index bc8191d2af46..af47666efa5a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -130,6 +130,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private DisplayController mDisplayController;
private PipInputConsumer mPipInputConsumer;
private WindowManagerShellWrapper mWindowManagerShellWrapper;
+ private PipAnimationController mPipAnimationController;
private PipAppOpsListener mAppOpsListener;
private PipMediaController mMediaController;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
@@ -158,7 +159,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return;
}
// if there is another animation ongoing, wait for it to finish and try again
- if (mPipTaskOrganizer.isAnimating()) {
+ if (mPipAnimationController.isAnimating()) {
mMainExecutor.removeCallbacks(
mMovePipInResponseToKeepClearAreasChangeCallback);
mMainExecutor.executeDelayed(
@@ -368,6 +369,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
+ PipAnimationController pipAnimationController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -392,11 +394,12 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
return new PipController(context, shellInit, shellCommandHandler, shellController,
- displayController, pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm,
- pipBoundsState, pipMotionHelper, pipMediaController, phonePipMenuController,
- pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
- windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
- displayInsetsController, oneHandedController, mainExecutor)
+ displayController, pipAnimationController, pipAppOpsListener,
+ pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
+ pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
+ taskStackListener, pipParamsChangedForwarder, displayInsetsController,
+ oneHandedController, mainExecutor)
.mImpl;
}
@@ -405,6 +408,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
+ PipAnimationController pipAnimationController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
PipKeepClearAlgorithm pipKeepClearAlgorithm,
@@ -445,6 +449,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
mMediaController = pipMediaController;
mMenuController = phonePipMenuController;
mTouchHandler = pipTouchHandler;
+ mPipAnimationController = pipAnimationController;
mAppOpsListener = pipAppOpsListener;
mOneHandedController = oneHandedController;
mPipTransitionController = pipTransitionController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
index a5748f69388f..2a625524b48b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java
@@ -17,6 +17,11 @@
package com.android.wm.shell.recents;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.util.GroupedRecentTaskInfo;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Interface for interacting with the recent tasks.
@@ -29,4 +34,11 @@ public interface RecentTasks {
default IRecentTasks createExternalInterface() {
return null;
}
+
+ /**
+ * Gets the set of recent tasks.
+ */
+ default void getRecentTasks(int maxNum, int flags, int userId, Executor callbackExecutor,
+ Consumer<List<GroupedRecentTaskInfo>> callback) {
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index 6409e70b0752..02b5a35f653b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -58,6 +58,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Manages the recent task list from the system, caching it as necessary.
@@ -72,6 +74,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
private final ShellExecutor mMainExecutor;
private final TaskStackListenerImpl mTaskStackListener;
private final RecentTasks mImpl = new RecentTasksImpl();
+ private final ActivityTaskManager mActivityTaskManager;
private IRecentTasksListener mListener;
private final boolean mIsDesktopMode;
@@ -96,6 +99,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
+ ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
@ShellMainThread ShellExecutor mainExecutor
) {
@@ -103,17 +107,19 @@ public class RecentTasksController implements TaskStackListenerCallback,
return null;
}
return new RecentTasksController(context, shellInit, shellCommandHandler, taskStackListener,
- desktopModeTaskRepository, mainExecutor);
+ activityTaskManager, desktopModeTaskRepository, mainExecutor);
}
RecentTasksController(Context context,
ShellInit shellInit,
ShellCommandHandler shellCommandHandler,
TaskStackListenerImpl taskStackListener,
+ ActivityTaskManager activityTaskManager,
Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
ShellExecutor mainExecutor) {
mContext = context;
mShellCommandHandler = shellCommandHandler;
+ mActivityTaskManager = activityTaskManager;
mIsDesktopMode = mContext.getPackageManager().hasSystemFeature(FEATURE_PC);
mTaskStackListener = taskStackListener;
mDesktopModeTaskRepository = desktopModeTaskRepository;
@@ -270,15 +276,10 @@ public class RecentTasksController implements TaskStackListenerCallback,
}
@VisibleForTesting
- List<ActivityManager.RecentTaskInfo> getRawRecentTasks(int maxNum, int flags, int userId) {
- return ActivityTaskManager.getInstance().getRecentTasks(maxNum, flags, userId);
- }
-
- @VisibleForTesting
ArrayList<GroupedRecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) {
// Note: the returned task list is from the most-recent to least-recent order
- final List<ActivityManager.RecentTaskInfo> rawList = getRawRecentTasks(maxNum, flags,
- userId);
+ final List<ActivityManager.RecentTaskInfo> rawList = mActivityTaskManager.getRecentTasks(
+ maxNum, flags, userId);
// Make a mapping of task id -> task info
final SparseArray<ActivityManager.RecentTaskInfo> rawMapping = new SparseArray<>();
@@ -335,8 +336,9 @@ public class RecentTasksController implements TaskStackListenerCallback,
if (componentName == null) {
return null;
}
- List<ActivityManager.RecentTaskInfo> tasks = getRawRecentTasks(Integer.MAX_VALUE,
- ActivityManager.RECENT_IGNORE_UNAVAILABLE, ActivityManager.getCurrentUser());
+ List<ActivityManager.RecentTaskInfo> tasks = mActivityTaskManager.getRecentTasks(
+ Integer.MAX_VALUE, ActivityManager.RECENT_IGNORE_UNAVAILABLE,
+ ActivityManager.getCurrentUser());
for (int i = 0; i < tasks.size(); i++) {
final ActivityManager.RecentTaskInfo task = tasks.get(i);
if (task.isVisible) {
@@ -374,6 +376,16 @@ public class RecentTasksController implements TaskStackListenerCallback,
mIRecentTasks = new IRecentTasksImpl(RecentTasksController.this);
return mIRecentTasks;
}
+
+ @Override
+ public void getRecentTasks(int maxNum, int flags, int userId, Executor executor,
+ Consumer<List<GroupedRecentTaskInfo>> callback) {
+ mMainExecutor.execute(() -> {
+ List<GroupedRecentTaskInfo> tasks =
+ RecentTasksController.this.getRecentTasks(maxNum, flags, userId);
+ executor.execute(() -> callback.accept(tasks));
+ });
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index 9c7131a9e02e..9e49b51e1504 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -36,6 +37,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.DesktopModeController;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter;
import com.android.wm.shell.transition.Transitions;
@@ -53,6 +55,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
private final DisplayController mDisplayController;
private final SyncTransactionQueue mSyncQueue;
private FreeformTaskTransitionStarter mTransitionStarter;
+ private DesktopModeController mDesktopModeController;
public CaptionWindowDecorViewModel(
Context context,
@@ -60,7 +63,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
Choreographer mainChoreographer,
ShellTaskOrganizer taskOrganizer,
DisplayController displayController,
- SyncTransactionQueue syncQueue) {
+ SyncTransactionQueue syncQueue,
+ DesktopModeController desktopModeController) {
mContext = context;
mMainHandler = mainHandler;
mMainChoreographer = mainChoreographer;
@@ -68,6 +72,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
mTaskOrganizer = taskOrganizer;
mDisplayController = displayController;
mSyncQueue = syncQueue;
+ mDesktopModeController = desktopModeController;
}
@Override
@@ -210,8 +215,10 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
}
private void handleEventForMove(MotionEvent e) {
- if (mTaskOrganizer.getRunningTaskInfo(mTaskId).getWindowingMode()
- == WINDOWING_MODE_FULLSCREEN) {
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ int windowingMode = mDesktopModeController
+ .getDisplayAreaWindowingMode(taskInfo.displayId);
+ if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
return;
}
switch (e.getActionMasked()) {
@@ -229,8 +236,14 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId)
+ .stableInsets().top;
mDragResizeCallback.onDragResizeEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ if (e.getRawY(dragPointerIdx) <= statusBarHeight
+ && windowingMode == WINDOWING_MODE_FREEFORM) {
+ mDesktopModeController.setDesktopModeActive(false);
+ }
break;
}
}
@@ -240,6 +253,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel<Caption
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
return DesktopModeStatus.IS_SUPPORTED
+ && taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& mDisplayController.getDisplayContext(taskInfo.displayId)
.getResources().getConfiguration().smallestScreenWidthDp >= 600;
}
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 2ccf1216d7c1..6d133779ea60 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
@@ -20,7 +20,6 @@ package com.android.wm.shell.flicker
import android.view.Surface
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.traces.layers.LayerTraceEntrySubject
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject
import com.android.server.wm.traces.common.IComponentMatcher
@@ -46,6 +45,66 @@ fun FlickerTestParameter.appPairsDividerBecomesVisible() {
}
}
+fun FlickerTestParameter.splitScreenEntered(
+ component1: IComponentMatcher,
+ component2: IComponentMatcher,
+ fromOtherApp: Boolean
+) {
+ if (fromOtherApp) {
+ appWindowIsInvisibleAtStart(component1)
+ } else {
+ appWindowIsVisibleAtStart(component1)
+ }
+ appWindowIsInvisibleAtStart(component2)
+ splitScreenDividerIsInvisibleAtStart()
+
+ appWindowIsVisibleAtEnd(component1)
+ appWindowIsVisibleAtEnd(component2)
+ splitScreenDividerIsVisibleAtEnd()
+}
+
+fun FlickerTestParameter.splitScreenDismissed(
+ component1: IComponentMatcher,
+ component2: IComponentMatcher,
+ toHome: Boolean
+) {
+ appWindowIsVisibleAtStart(component1)
+ appWindowIsVisibleAtStart(component2)
+ splitScreenDividerIsVisibleAtStart()
+
+ appWindowIsInvisibleAtEnd(component1)
+ if (toHome) {
+ appWindowIsInvisibleAtEnd(component2)
+ } else {
+ appWindowIsVisibleAtEnd(component2)
+ }
+ splitScreenDividerIsInvisibleAtEnd()
+}
+
+fun FlickerTestParameter.splitScreenDividerIsVisibleAtStart() {
+ assertLayersStart {
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsVisibleAtEnd() {
+ assertLayersEnd {
+ this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsInvisibleAtStart() {
+ assertLayersStart {
+ this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+}
+
+fun FlickerTestParameter.splitScreenDividerIsInvisibleAtEnd() {
+ assertLayersEnd {
+ this.isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+ }
+}
+
fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
}
@@ -113,25 +172,12 @@ fun FlickerTestParameter.splitAppLayerBoundsBecomesVisibleByDrag(
component: IComponentMatcher
) {
assertLayers {
- if (isShellTransitionsEnabled) {
- this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
- .then()
- .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
- .then()
- // TODO(b/245472831): Verify the component should snap to divider.
- .isVisible(component)
- } else {
- this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
- .then()
- .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
- .then()
- // TODO(b/245472831): Verify the component should snap to divider.
- .isVisible(component)
- .then()
- .isInvisible(component, isOptional = true)
- .then()
- .isVisible(component)
- }
+ this.notContains(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component), isOptional = true)
+ .then()
+ .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT.or(component))
+ .then()
+ // TODO(b/245472831): Verify the component should snap to divider.
+ .isVisible(component)
}
}
@@ -271,6 +317,14 @@ fun FlickerTestParameter.appWindowBecomesInvisible(
}
}
+fun FlickerTestParameter.appWindowIsVisibleAtStart(
+ component: IComponentMatcher
+) {
+ assertWmStart {
+ this.isAppWindowVisible(component)
+ }
+}
+
fun FlickerTestParameter.appWindowIsVisibleAtEnd(
component: IComponentMatcher
) {
@@ -279,6 +333,22 @@ fun FlickerTestParameter.appWindowIsVisibleAtEnd(
}
}
+fun FlickerTestParameter.appWindowIsInvisibleAtStart(
+ component: IComponentMatcher
+) {
+ assertWmStart {
+ this.isAppWindowInvisible(component)
+ }
+}
+
+fun FlickerTestParameter.appWindowIsInvisibleAtEnd(
+ component: IComponentMatcher
+) {
+ assertWmEnd {
+ this.isAppWindowInvisible(component)
+ }
+}
+
fun FlickerTestParameter.appWindowKeepVisible(
component: IComponentMatcher
) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 7dbd27949bdd..5b044e3fcbc3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -27,9 +27,13 @@ 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.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsKeepVisible
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -64,36 +68,44 @@ class CopyContentInSplit(testSpec: FlickerTestParameter) : SplitScreenBase(testS
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() {
+ testSpec.appWindowIsVisibleAtStart(primaryApp)
+ testSpec.appWindowIsVisibleAtStart(textEditApp)
+ testSpec.splitScreenDividerIsVisibleAtStart()
+
+ testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ testSpec.appWindowIsVisibleAtEnd(textEditApp)
+ testSpec.splitScreenDividerIsVisibleAtEnd()
+
+ // The validation of copied text is already done in SplitScreenUtils.copyContentInSplit()
+ }
+
+ @Presubmit
+ @Test
fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun textEditAppLayerKeepVisible() = testSpec.layerKeepVisible(textEditApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun textEditAppBoundsKeepVisible() = testSpec.splitAppLayerBoundsKeepVisible(
textEditApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun textEditAppWindowKeepVisible() = testSpec.appWindowKeepVisible(textEditApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 3646fd7f0177..838026fdc6a6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -33,6 +33,7 @@ import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
import org.junit.FixMethodOrder
import org.junit.Test
@@ -75,25 +76,25 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = false)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsIsFullscreenAtEnd() {
@@ -116,12 +117,10 @@ class DismissSplitScreenByDivider (testSpec: FlickerTestParameter) : SplitScreen
}
}
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 80abedd476e6..a764206fb33e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -30,6 +30,7 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.wm.shell.flicker.appWindowBecomesInvisible
import com.android.wm.shell.flicker.layerBecomesInvisible
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesInvisible
+import com.android.wm.shell.flicker.splitScreenDismissed
import com.android.wm.shell.flicker.splitScreenDividerBecomesInvisible
import org.junit.FixMethodOrder
import org.junit.Test
@@ -64,13 +65,15 @@ class DismissSplitScreenByGoHome(
.waitForAndVerify()
}
}
-
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenDismissed(primaryApp, secondaryApp, toHome = true)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesInvisible() = testSpec.splitScreenDividerBecomesInvisible()
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(primaryApp)
@@ -86,18 +89,15 @@ class DismissSplitScreenByGoHome(
fun primaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsBecomesInvisible() = testSpec.splitAppLayerBoundsBecomesInvisible(
secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowBecomesInvisible() = testSpec.appWindowBecomesInvisible(secondaryApp)
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 2915787d5a43..ba0231755690 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
@@ -28,9 +28,13 @@ 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.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.appWindowKeepVisible
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsChanges
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -63,14 +67,27 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() {
+ testSpec.appWindowIsVisibleAtStart(primaryApp)
+ testSpec.appWindowIsVisibleAtStart(secondaryApp)
+ testSpec.splitScreenDividerIsVisibleAtStart()
+
+ testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+ testSpec.splitScreenDividerIsVisibleAtEnd()
+
+ // TODO(b/246490534): Add validation for resized app after withAppTransitionIdle is
+ // robust enough to get the correct end state.
+ }
+
+ @Presubmit
+ @Test
fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerKeepVisible() = testSpec.layerKeepVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerVisibilityChanges() {
@@ -83,23 +100,19 @@ class DragDividerToResize (testSpec: FlickerTestParameter) : SplitScreenBase(tes
}
}
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowKeepVisible() = testSpec.appWindowKeepVisible(secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
primaryApp, landscapePosLeft = true, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsChanges() = testSpec.splitAppLayerBoundsChanges(
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index 8e041a703802..facbcababf9c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -35,6 +35,7 @@ import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -82,13 +83,16 @@ class EnterSplitScreenByDragFromAllApps(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
testSpec.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -98,53 +102,28 @@ class EnterSplitScreenByDragFromAllApps(
}
}
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
- @Presubmit
- @Test
- fun secondaryAppLayerBecomesVisible() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- testSpec.assertLayers {
- this.isInvisible(secondaryApp)
- .then()
- .isVisible(secondaryApp)
- .then()
- .isInvisible(secondaryApp)
- .then()
- .isVisible(secondaryApp)
- }
- }
-
- // TODO(b/245472831): Align to legacy transition after shell transition ready.
@Presubmit
@Test
- fun secondaryAppLayerBecomesVisible_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
- testSpec.layerBecomesVisible(secondaryApp)
- }
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp, landscapePosLeft = false, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index 2ee12f1f0af2..e208196000fc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -34,6 +34,7 @@ import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -87,13 +88,16 @@ class EnterSplitScreenByDragFromNotification(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
testSpec.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -103,12 +107,10 @@ class EnterSplitScreenByDragFromNotification(
}
}
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
@@ -132,24 +134,20 @@ class EnterSplitScreenByDragFromNotification(
testSpec.layerBecomesVisible(sendNotificationApp)
}
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp, landscapePosLeft = false, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
sendNotificationApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(sendNotificationApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index a11874e00228..84d2e6a42d27 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -35,6 +35,7 @@ import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.Assume
import org.junit.Before
import org.junit.FixMethodOrder
@@ -85,13 +86,16 @@ class EnterSplitScreenByDragFromTaskbar(
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = false)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesVisible() {
Assume.assumeFalse(isShellTransitionsEnabled)
testSpec.splitScreenDividerBecomesVisible()
}
// TODO(b/245472831): Back to splitScreenDividerBecomesVisible after shell transition ready.
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun splitScreenDividerIsVisibleAtEnd_ShellTransit() {
@@ -101,12 +105,10 @@ class EnterSplitScreenByDragFromTaskbar(
}
}
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() {
@@ -130,24 +132,20 @@ class EnterSplitScreenByDragFromTaskbar(
testSpec.layerBecomesVisible(secondaryApp)
}
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp, landscapePosLeft = false, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 6064b52938c8..23623aaf2d00 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -31,6 +31,7 @@ import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -71,36 +72,34 @@ class EnterSplitScreenFromOverview(testSpec: FlickerTestParameter) : SplitScreen
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
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 f06dd66f61d2..025bb4085624 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
@@ -31,9 +31,12 @@ import com.android.server.wm.flicker.helpers.isRotated
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
import com.android.wm.shell.flicker.layerIsVisibleAtEnd
import com.android.wm.shell.flicker.layerKeepVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -111,19 +114,31 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() {
+ testSpec.appWindowIsVisibleAtStart(primaryApp)
+ testSpec.appWindowIsVisibleAtStart(secondaryApp)
+ testSpec.splitScreenDividerIsVisibleAtStart()
+
+ testSpec.appWindowIsVisibleAtEnd(primaryApp)
+ testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+ testSpec.splitScreenDividerIsVisibleAtEnd()
+
+ // TODO(b/246490534): Add validation for switched app after withAppTransitionIdle is
+ // robust enough to get the correct end state.
+ }
+
+ @Presubmit
+ @Test
fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
@@ -136,12 +151,10 @@ class SwitchAppByDoubleTapDivider(testSpec: FlickerTestParameter) : SplitScreenB
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 5c3011643270..9947a53dda42 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -30,6 +30,7 @@ import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -69,36 +70,34 @@ class SwitchBackToSplitFromAnotherApp(testSpec: FlickerTestParameter) : SplitScr
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 9c66a3734da3..3716dc98714c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -30,6 +30,7 @@ import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -68,36 +69,34 @@ class SwitchBackToSplitFromHome(testSpec: FlickerTestParameter) : SplitScreenBas
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index e8862bd4c982..db07f2132e95 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -30,6 +30,7 @@ import com.android.wm.shell.flicker.appWindowBecomesVisible
import com.android.wm.shell.flicker.layerBecomesVisible
import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -70,36 +71,34 @@ class SwitchBackToSplitFromRecent(testSpec: FlickerTestParameter) : SplitScreenB
@IwTest(focusArea = "sysui")
@Presubmit
@Test
+ fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp, fromOtherApp = true)
+
+ @Presubmit
+ @Test
fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
primaryApp, landscapePosLeft = tapl.isTablet, portraitPosTop = false)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
secondaryApp, landscapePosLeft = !tapl.isTablet, portraitPosTop = true)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
- @IwTest(focusArea = "sysui")
@Presubmit
@Test
fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index a8d3bdcb7c96..1e08f1e55797 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -48,6 +48,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipAppOpsListener;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
@@ -85,6 +86,7 @@ public class PipControllerTest extends ShellTestCase {
@Mock private ShellCommandHandler mMockShellCommandHandler;
@Mock private DisplayController mMockDisplayController;
@Mock private PhonePipMenuController mMockPhonePipMenuController;
+ @Mock private PipAnimationController mMockPipAnimationController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
@Mock private PhonePipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@@ -117,8 +119,8 @@ public class PipControllerTest extends ShellTestCase {
mShellController = spy(new ShellController(mShellInit, mMockShellCommandHandler,
mMockExecutor));
mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
- mShellController, mMockDisplayController, mMockPipAppOpsListener,
- mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+ mShellController, mMockDisplayController, mMockPipAnimationController,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -183,8 +185,8 @@ public class PipControllerTest extends ShellTestCase {
ShellInit shellInit = new ShellInit(mMockExecutor);
assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
- mShellController, mMockDisplayController, mMockPipAppOpsListener,
- mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
+ mShellController, mMockDisplayController, mMockPipAnimationController,
+ mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 70fee2be90c6..b8aaaa76e3c7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -40,6 +40,7 @@ import static org.mockito.Mockito.when;
import static java.lang.Integer.MAX_VALUE;
import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Rect;
@@ -52,7 +53,6 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.desktopmode.DesktopModeStatus;
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -68,7 +68,9 @@ import org.mockito.Mock;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
import java.util.Optional;
+import java.util.function.Consumer;
/**
* Tests for {@link RecentTasksController}.
@@ -85,11 +87,13 @@ public class RecentTasksControllerTest extends ShellTestCase {
private ShellCommandHandler mShellCommandHandler;
@Mock
private DesktopModeTaskRepository mDesktopModeTaskRepository;
+ @Mock
+ private ActivityTaskManager mActivityTaskManager;
private ShellTaskOrganizer mShellTaskOrganizer;
private RecentTasksController mRecentTasksController;
private ShellInit mShellInit;
- private ShellExecutor mMainExecutor;
+ private TestShellExecutor mMainExecutor;
@Before
public void setUp() {
@@ -97,8 +101,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
mShellInit = spy(new ShellInit(mMainExecutor));
mRecentTasksController = spy(new RecentTasksController(mContext, mShellInit,
- mShellCommandHandler, mTaskStackListener, Optional.of(mDesktopModeTaskRepository),
- mMainExecutor));
+ mShellCommandHandler, mTaskStackListener, mActivityTaskManager,
+ Optional.of(mDesktopModeTaskRepository), mMainExecutor));
mShellTaskOrganizer = new ShellTaskOrganizer(mShellInit, mShellCommandHandler,
null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController),
mMainExecutor);
@@ -188,6 +192,37 @@ public class RecentTasksControllerTest extends ShellTestCase {
}
@Test
+ public void testGetRecentTasks_ReturnsRecentTasksAsynchronously() {
+ @SuppressWarnings("unchecked")
+ final List<GroupedRecentTaskInfo>[] recentTasks = new List[1];
+ Consumer<List<GroupedRecentTaskInfo>> consumer = argument -> recentTasks[0] = argument;
+ ActivityManager.RecentTaskInfo t1 = makeTaskInfo(1);
+ ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
+ ActivityManager.RecentTaskInfo t3 = makeTaskInfo(3);
+ ActivityManager.RecentTaskInfo t4 = makeTaskInfo(4);
+ ActivityManager.RecentTaskInfo t5 = makeTaskInfo(5);
+ ActivityManager.RecentTaskInfo t6 = makeTaskInfo(6);
+ setRawList(t1, t2, t3, t4, t5, t6);
+
+ // Mark a couple pairs [t2, t4], [t3, t5]
+ SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
+ SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
+
+ mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
+ mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
+
+ mRecentTasksController.asRecentTasks()
+ .getRecentTasks(MAX_VALUE, RECENT_IGNORE_UNAVAILABLE, 0, Runnable::run, consumer);
+ mMainExecutor.flushAll();
+
+ assertGroupedTasksListEquals(recentTasks[0],
+ t1.taskId, -1,
+ t2.taskId, t4.taskId,
+ t3.taskId, t5.taskId,
+ t6.taskId, -1);
+ }
+
+ @Test
public void testGetRecentTasks_groupActiveFreeformTasks() {
StaticMockitoSession mockitoSession = mockitoSession().mockStatic(
DesktopModeStatus.class).startMocking();
@@ -296,7 +331,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
for (ActivityManager.RecentTaskInfo task : tasks) {
rawList.add(task);
}
- doReturn(rawList).when(mRecentTasksController).getRawRecentTasks(anyInt(), anyInt(),
+ doReturn(rawList).when(mActivityTaskManager).getRecentTasks(anyInt(), anyInt(),
anyInt());
return rawList;
}
@@ -307,7 +342,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
* @param expectedTaskIds list of task ids that map to the flattened task ids of the tasks in
* the grouped task list
*/
- private void assertGroupedTasksListEquals(ArrayList<GroupedRecentTaskInfo> recentTasks,
+ private void assertGroupedTasksListEquals(List<GroupedRecentTaskInfo> recentTasks,
int... expectedTaskIds) {
int[] flattenedTaskIds = new int[recentTasks.size() * 2];
for (int i = 0; i < recentTasks.size(); i++) {
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b11e542472da..29f37737d433 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -521,6 +521,7 @@ cc_defaults {
"Interpolator.cpp",
"LightingInfo.cpp",
"Matrix.cpp",
+ "MemoryPolicy.cpp",
"PathParser.cpp",
"Properties.cpp",
"PropertyValuesAnimatorSet.cpp",
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index 07594715a84c..f06fa2418003 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -93,12 +93,14 @@ void DeviceInfo::setWideColorDataspace(ADataSpace dataspace) {
case ADATASPACE_SCRGB:
get()->mWideColorSpace = SkColorSpace::MakeSRGB();
break;
+ default:
+ ALOGW("Unknown dataspace %d", dataspace);
+ // Treat unknown dataspaces as sRGB, so fall through
+ [[fallthrough]];
case ADATASPACE_SRGB:
// when sRGB is returned, it means wide color gamut is not supported.
get()->mWideColorSpace = SkColorSpace::MakeSRGB();
break;
- default:
- LOG_ALWAYS_FATAL("Unreachable: unsupported wide color space.");
}
}
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 7291cab364e2..b7e99994355c 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -42,6 +42,8 @@
namespace android::uirenderer {
+static constexpr auto kThreadTimeout = 60000_ms;
+
class AHBUploader;
// This helper uploader classes allows us to upload using either EGL or Vulkan using the same
// interface.
@@ -80,7 +82,7 @@ public:
}
void postIdleTimeoutCheck() {
- mUploadThread->queue().postDelayed(5000_ms, [this](){ this->idleTimeoutCheck(); });
+ mUploadThread->queue().postDelayed(kThreadTimeout, [this]() { this->idleTimeoutCheck(); });
}
protected:
@@ -97,7 +99,7 @@ private:
bool shouldTimeOutLocked() {
nsecs_t durationSince = systemTime() - mLastUpload;
- return durationSince > 2000_ms;
+ return durationSince > kThreadTimeout;
}
void idleTimeoutCheck() {
diff --git a/libs/hwui/MemoryPolicy.cpp b/libs/hwui/MemoryPolicy.cpp
new file mode 100644
index 000000000000..ca1312e75f4c
--- /dev/null
+++ b/libs/hwui/MemoryPolicy.cpp
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#include "MemoryPolicy.h"
+
+#include <android-base/properties.h>
+
+#include <optional>
+#include <string_view>
+
+#include "Properties.h"
+
+namespace android::uirenderer {
+
+constexpr static MemoryPolicy sDefaultMemoryPolicy;
+constexpr static MemoryPolicy sPersistentOrSystemPolicy{
+ .contextTimeout = 10_s,
+ .useAlternativeUiHidden = true,
+};
+constexpr static MemoryPolicy sLowRamPolicy{
+ .useAlternativeUiHidden = true,
+ .purgeScratchOnly = false,
+};
+constexpr static MemoryPolicy sExtremeLowRam{
+ .initialMaxSurfaceAreaScale = 0.2f,
+ .surfaceSizeMultiplier = 5 * 4.0f,
+ .backgroundRetentionPercent = 0.2f,
+ .contextTimeout = 5_s,
+ .minimumResourceRetention = 1_s,
+ .useAlternativeUiHidden = true,
+ .purgeScratchOnly = false,
+ .releaseContextOnStoppedOnly = true,
+};
+
+const MemoryPolicy& loadMemoryPolicy() {
+ if (Properties::isSystemOrPersistent) {
+ return sPersistentOrSystemPolicy;
+ }
+ std::string memoryPolicy = base::GetProperty(PROPERTY_MEMORY_POLICY, "");
+ if (memoryPolicy == "default") {
+ return sDefaultMemoryPolicy;
+ }
+ if (memoryPolicy == "lowram") {
+ return sLowRamPolicy;
+ }
+ if (memoryPolicy == "extremelowram") {
+ return sExtremeLowRam;
+ }
+
+ if (Properties::isLowRam) {
+ return sLowRamPolicy;
+ }
+ return sDefaultMemoryPolicy;
+}
+
+} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/MemoryPolicy.h b/libs/hwui/MemoryPolicy.h
new file mode 100644
index 000000000000..e86b338608d2
--- /dev/null
+++ b/libs/hwui/MemoryPolicy.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "utils/TimeUtils.h"
+
+namespace android::uirenderer {
+
+// Values mirror those from ComponentCallbacks2.java
+enum class TrimLevel {
+ COMPLETE = 80,
+ MODERATE = 60,
+ BACKGROUND = 40,
+ UI_HIDDEN = 20,
+ RUNNING_CRITICAL = 15,
+ RUNNING_LOW = 10,
+ RUNNING_MODERATE = 5,
+};
+
+struct MemoryPolicy {
+ // The initial scale factor applied to the display resolution. The default is 1, but
+ // lower values may be used to start with a smaller initial cache size. The cache will
+ // be adjusted if larger frames are actually rendered
+ float initialMaxSurfaceAreaScale = 1.0f;
+ // The foreground cache size multiplier. The surface area of the screen will be multiplied
+ // by this
+ float surfaceSizeMultiplier = 12.0f * 4.0f;
+ // How much of the foreground cache size should be preserved when going into the background
+ float backgroundRetentionPercent = 0.5f;
+ // How long after the last renderer goes away before the GPU context is released. A value
+ // of 0 means only drop the context on background TRIM signals
+ nsecs_t contextTimeout = 0_ms;
+ // The minimum amount of time to hold onto items in the resource cache
+ // The actual time used will be the max of this & when frames were actually rendered
+ nsecs_t minimumResourceRetention = 10_s;
+ // If false, use only TRIM_UI_HIDDEN to drive background cache limits;
+ // If true, use all signals (such as all contexts are stopped) to drive the limits
+ bool useAlternativeUiHidden = false;
+ // Whether or not to only purge scratch resources when triggering UI Hidden or background
+ // collection
+ bool purgeScratchOnly = true;
+ // EXPERIMENTAL: Whether or not to trigger releasing GPU context when all contexts are stopped
+ bool releaseContextOnStoppedOnly = false;
+};
+
+const MemoryPolicy& loadMemoryPolicy();
+
+} // namespace android::uirenderer \ No newline at end of file
diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp
index 5a67eb9935dd..277955e88dfe 100644
--- a/libs/hwui/Properties.cpp
+++ b/libs/hwui/Properties.cpp
@@ -87,6 +87,10 @@ int Properties::targetCpuTimePercentage = 70;
bool Properties::enableWebViewOverlays = true;
+bool Properties::isHighEndGfx = true;
+bool Properties::isLowRam = false;
+bool Properties::isSystemOrPersistent = false;
+
StretchEffectBehavior Properties::stretchEffectBehavior = StretchEffectBehavior::ShaderHWUI;
DrawingEnabled Properties::drawingEnabled = DrawingEnabled::NotInitialized;
diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h
index 2f8c67903a8b..96a517629eaa 100644
--- a/libs/hwui/Properties.h
+++ b/libs/hwui/Properties.h
@@ -193,6 +193,8 @@ enum DebugLevel {
*/
#define PROPERTY_DRAWING_ENABLED "debug.hwui.drawing_enabled"
+#define PROPERTY_MEMORY_POLICY "debug.hwui.app_memory_policy"
+
///////////////////////////////////////////////////////////////////////////////
// Misc
///////////////////////////////////////////////////////////////////////////////
@@ -292,16 +294,27 @@ public:
static bool enableWebViewOverlays;
+ static bool isHighEndGfx;
+ static bool isLowRam;
+ static bool isSystemOrPersistent;
+
static StretchEffectBehavior getStretchEffectBehavior() {
return stretchEffectBehavior;
}
static void setIsHighEndGfx(bool isHighEndGfx) {
+ Properties::isHighEndGfx = isHighEndGfx;
stretchEffectBehavior = isHighEndGfx ?
StretchEffectBehavior::ShaderHWUI :
StretchEffectBehavior::UniformScale;
}
+ static void setIsLowRam(bool isLowRam) { Properties::isLowRam = isLowRam; }
+
+ static void setIsSystemOrPersistent(bool isSystemOrPersistent) {
+ Properties::isSystemOrPersistent = isSystemOrPersistent;
+ }
+
/**
* Used for testing. Typical configuration of stretch behavior is done
* through setIsHighEndGfx
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index 4f281fcde61d..704fba9241b6 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -271,6 +271,16 @@ static void android_view_ThreadedRenderer_setIsHighEndGfx(JNIEnv* env, jobject c
Properties::setIsHighEndGfx(jIsHighEndGfx);
}
+static void android_view_ThreadedRenderer_setIsLowRam(JNIEnv* env, jobject clazz,
+ jboolean isLowRam) {
+ Properties::setIsLowRam(isLowRam);
+}
+
+static void android_view_ThreadedRenderer_setIsSystemOrPersistent(JNIEnv* env, jobject clazz,
+ jboolean isSystemOrPersistent) {
+ Properties::setIsSystemOrPersistent(isSystemOrPersistent);
+}
+
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo,
jint frameInfoSize) {
@@ -949,6 +959,9 @@ static const JNINativeMethod gMethods[] = {
{"nSetColorMode", "(JI)V", (void*)android_view_ThreadedRenderer_setColorMode},
{"nSetSdrWhitePoint", "(JF)V", (void*)android_view_ThreadedRenderer_setSdrWhitePoint},
{"nSetIsHighEndGfx", "(Z)V", (void*)android_view_ThreadedRenderer_setIsHighEndGfx},
+ {"nSetIsLowRam", "(Z)V", (void*)android_view_ThreadedRenderer_setIsLowRam},
+ {"nSetIsSystemOrPersistent", "(Z)V",
+ (void*)android_view_ThreadedRenderer_setIsSystemOrPersistent},
{"nSyncAndDrawFrame", "(J[JI)I", (void*)android_view_ThreadedRenderer_syncAndDrawFrame},
{"nDestroy", "(JJ)V", (void*)android_view_ThreadedRenderer_destroy},
{"nRegisterAnimatingRenderNode", "(JJ)V",
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index ded2b06fb3cf..1d24e718db1a 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -16,6 +16,16 @@
#include "CacheManager.h"
+#include <GrContextOptions.h>
+#include <SkExecutor.h>
+#include <SkGraphics.h>
+#include <SkMathPriv.h>
+#include <math.h>
+#include <utils/Trace.h>
+
+#include <set>
+
+#include "CanvasContext.h"
#include "DeviceInfo.h"
#include "Layer.h"
#include "Properties.h"
@@ -25,40 +35,34 @@
#include "pipeline/skia/SkiaMemoryTracer.h"
#include "renderstate/RenderState.h"
#include "thread/CommonPool.h"
-#include <utils/Trace.h>
-
-#include <GrContextOptions.h>
-#include <SkExecutor.h>
-#include <SkGraphics.h>
-#include <SkMathPriv.h>
-#include <math.h>
-#include <set>
namespace android {
namespace uirenderer {
namespace renderthread {
-// This multiplier was selected based on historical review of cache sizes relative
-// to the screen resolution. This is meant to be a conservative default based on
-// that analysis. The 4.0f is used because the default pixel format is assumed to
-// be ARGB_8888.
-#define SURFACE_SIZE_MULTIPLIER (12.0f * 4.0f)
-#define BACKGROUND_RETENTION_PERCENTAGE (0.5f)
-
-CacheManager::CacheManager()
- : mMaxSurfaceArea(DeviceInfo::getWidth() * DeviceInfo::getHeight())
- , mMaxResourceBytes(mMaxSurfaceArea * SURFACE_SIZE_MULTIPLIER)
- , mBackgroundResourceBytes(mMaxResourceBytes * BACKGROUND_RETENTION_PERCENTAGE)
- // This sets the maximum size for a single texture atlas in the GPU font cache. If
- // necessary, the cache can allocate additional textures that are counted against the
- // total cache limits provided to Skia.
- , mMaxGpuFontAtlasBytes(GrNextSizePow2(mMaxSurfaceArea))
- // This sets the maximum size of the CPU font cache to be at least the same size as the
- // total number of GPU font caches (i.e. 4 separate GPU atlases).
- , mMaxCpuFontCacheBytes(
- std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit()))
- , mBackgroundCpuFontCacheBytes(mMaxCpuFontCacheBytes * BACKGROUND_RETENTION_PERCENTAGE) {
+CacheManager::CacheManager(RenderThread& thread)
+ : mRenderThread(thread), mMemoryPolicy(loadMemoryPolicy()) {
+ mMaxSurfaceArea = static_cast<size_t>((DeviceInfo::getWidth() * DeviceInfo::getHeight()) *
+ mMemoryPolicy.initialMaxSurfaceAreaScale);
+ setupCacheLimits();
+}
+
+void CacheManager::setupCacheLimits() {
+ mMaxResourceBytes = mMaxSurfaceArea * mMemoryPolicy.surfaceSizeMultiplier;
+ mBackgroundResourceBytes = mMaxResourceBytes * mMemoryPolicy.backgroundRetentionPercent;
+ // This sets the maximum size for a single texture atlas in the GPU font cache. If
+ // necessary, the cache can allocate additional textures that are counted against the
+ // total cache limits provided to Skia.
+ mMaxGpuFontAtlasBytes = GrNextSizePow2(mMaxSurfaceArea);
+ // This sets the maximum size of the CPU font cache to be at least the same size as the
+ // total number of GPU font caches (i.e. 4 separate GPU atlases).
+ mMaxCpuFontCacheBytes = std::max(mMaxGpuFontAtlasBytes * 4, SkGraphics::GetFontCacheLimit());
+ mBackgroundCpuFontCacheBytes = mMaxCpuFontCacheBytes * mMemoryPolicy.backgroundRetentionPercent;
+
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
+ if (mGrContext) {
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ }
}
void CacheManager::reset(sk_sp<GrDirectContext> context) {
@@ -69,6 +73,7 @@ void CacheManager::reset(sk_sp<GrDirectContext> context) {
if (context) {
mGrContext = std::move(context);
mGrContext->setResourceCacheLimit(mMaxResourceBytes);
+ mLastDeferredCleanup = systemTime(CLOCK_MONOTONIC);
}
}
@@ -96,7 +101,7 @@ void CacheManager::configureContext(GrContextOptions* contextOptions, const void
contextOptions->fGpuPathRenderers &= ~GpuPathRenderers::kCoverageCounting;
}
-void CacheManager::trimMemory(TrimMemoryMode mode) {
+void CacheManager::trimMemory(TrimLevel mode) {
if (!mGrContext) {
return;
}
@@ -104,21 +109,28 @@ void CacheManager::trimMemory(TrimMemoryMode mode) {
// flush and submit all work to the gpu and wait for it to finish
mGrContext->flushAndSubmit(/*syncCpu=*/true);
+ if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) {
+ mode = TrimLevel::COMPLETE;
+ }
+
switch (mode) {
- case TrimMemoryMode::Complete:
+ case TrimLevel::COMPLETE:
mGrContext->freeGpuResources();
SkGraphics::PurgeAllCaches();
+ mRenderThread.destroyRenderingContext();
break;
- case TrimMemoryMode::UiHidden:
+ case TrimLevel::UI_HIDDEN:
// Here we purge all the unlocked scratch resources and then toggle the resources cache
// limits between the background and max amounts. This causes the unlocked resources
// that have persistent data to be purged in LRU order.
- mGrContext->purgeUnlockedResources(true);
mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
- mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
+ mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+ mGrContext->setResourceCacheLimit(mMaxResourceBytes);
SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
break;
+ default:
+ break;
}
}
@@ -147,11 +159,29 @@ void CacheManager::getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage) {
}
void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState) {
+ log.appendFormat(R"(Memory policy:
+ Max surface area: %zu
+ Max resource usage: %.2fMB (x%.0f)
+ Background retention: %.0f%% (altUiHidden = %s)
+)",
+ mMaxSurfaceArea, mMaxResourceBytes / 1000000.f,
+ mMemoryPolicy.surfaceSizeMultiplier,
+ mMemoryPolicy.backgroundRetentionPercent * 100.0f,
+ mMemoryPolicy.useAlternativeUiHidden ? "true" : "false");
+ if (Properties::isSystemOrPersistent) {
+ log.appendFormat(" IsSystemOrPersistent\n");
+ }
+ log.appendFormat(" GPU Context timeout: %" PRIu64 "\n", ns2s(mMemoryPolicy.contextTimeout));
+ size_t stoppedContexts = 0;
+ for (auto context : mCanvasContexts) {
+ if (context->isStopped()) stoppedContexts++;
+ }
+ log.appendFormat("Contexts: %zu (stopped = %zu)\n", mCanvasContexts.size(), stoppedContexts);
+
if (!mGrContext) {
- log.appendFormat("No valid cache instance.\n");
+ log.appendFormat("No GPU context.\n");
return;
}
-
std::vector<skiapipeline::ResourcePair> cpuResourceMap = {
{"skia/sk_resource_cache/bitmap_", "Bitmaps"},
{"skia/sk_resource_cache/rrect-blur_", "Masks"},
@@ -199,6 +229,8 @@ void CacheManager::dumpMemoryUsage(String8& log, const RenderState* renderState)
}
void CacheManager::onFrameCompleted() {
+ cancelDestroyContext();
+ mFrameCompletions.next() = systemTime(CLOCK_MONOTONIC);
if (ATRACE_ENABLED()) {
static skiapipeline::ATraceMemoryDump tracer;
tracer.startFrame();
@@ -210,11 +242,82 @@ void CacheManager::onFrameCompleted() {
}
}
-void CacheManager::performDeferredCleanup(nsecs_t cleanupOlderThanMillis) {
- if (mGrContext) {
- mGrContext->performDeferredCleanup(
- std::chrono::milliseconds(cleanupOlderThanMillis),
- /* scratchResourcesOnly */true);
+void CacheManager::onThreadIdle() {
+ if (!mGrContext || mFrameCompletions.size() == 0) return;
+
+ const nsecs_t now = systemTime(CLOCK_MONOTONIC);
+ // Rate limiting
+ if ((now - mLastDeferredCleanup) < 25_ms) {
+ mLastDeferredCleanup = now;
+ const nsecs_t frameCompleteNanos = mFrameCompletions[0];
+ const nsecs_t frameDiffNanos = now - frameCompleteNanos;
+ const nsecs_t cleanupMillis =
+ ns2ms(std::max(frameDiffNanos, mMemoryPolicy.minimumResourceRetention));
+ mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
+ mMemoryPolicy.purgeScratchOnly);
+ }
+}
+
+void CacheManager::scheduleDestroyContext() {
+ if (mMemoryPolicy.contextTimeout > 0) {
+ mRenderThread.queue().postDelayed(mMemoryPolicy.contextTimeout,
+ [this, genId = mGenerationId] {
+ if (mGenerationId != genId) return;
+ // GenID should have already stopped this, but just in
+ // case
+ if (!areAllContextsStopped()) return;
+ mRenderThread.destroyRenderingContext();
+ });
+ }
+}
+
+void CacheManager::cancelDestroyContext() {
+ if (mIsDestructionPending) {
+ mIsDestructionPending = false;
+ mGenerationId++;
+ }
+}
+
+bool CacheManager::areAllContextsStopped() {
+ for (auto context : mCanvasContexts) {
+ if (!context->isStopped()) return false;
+ }
+ return true;
+}
+
+void CacheManager::checkUiHidden() {
+ if (!mGrContext) return;
+
+ if (mMemoryPolicy.useAlternativeUiHidden && areAllContextsStopped()) {
+ trimMemory(TrimLevel::UI_HIDDEN);
+ }
+}
+
+void CacheManager::registerCanvasContext(CanvasContext* context) {
+ mCanvasContexts.push_back(context);
+ cancelDestroyContext();
+}
+
+void CacheManager::unregisterCanvasContext(CanvasContext* context) {
+ std::erase(mCanvasContexts, context);
+ checkUiHidden();
+ if (mCanvasContexts.empty()) {
+ scheduleDestroyContext();
+ }
+}
+
+void CacheManager::onContextStopped(CanvasContext* context) {
+ checkUiHidden();
+ if (mMemoryPolicy.releaseContextOnStoppedOnly && areAllContextsStopped()) {
+ scheduleDestroyContext();
+ }
+}
+
+void CacheManager::notifyNextFrameSize(int width, int height) {
+ int frameArea = width * height;
+ if (frameArea > mMaxSurfaceArea) {
+ mMaxSurfaceArea = frameArea;
+ setupCacheLimits();
}
}
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index af82672c6f23..d21ac9badc43 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -22,7 +22,11 @@
#endif
#include <SkSurface.h>
#include <utils/String8.h>
+
#include <vector>
+
+#include "MemoryPolicy.h"
+#include "utils/RingBuffer.h"
#include "utils/TimeUtils.h"
namespace android {
@@ -35,17 +39,15 @@ class RenderState;
namespace renderthread {
-class IRenderPipeline;
class RenderThread;
+class CanvasContext;
class CacheManager {
public:
- enum class TrimMemoryMode { Complete, UiHidden };
-
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
void configureContext(GrContextOptions* context, const void* identity, ssize_t size);
#endif
- void trimMemory(TrimMemoryMode mode);
+ void trimMemory(TrimLevel mode);
void trimStaleResources();
void dumpMemoryUsage(String8& log, const RenderState* renderState = nullptr);
void getMemoryUsage(size_t* cpuUsage, size_t* gpuUsage);
@@ -53,30 +55,50 @@ public:
size_t getCacheSize() const { return mMaxResourceBytes; }
size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
void onFrameCompleted();
+ void notifyNextFrameSize(int width, int height);
+
+ void onThreadIdle();
- void performDeferredCleanup(nsecs_t cleanupOlderThanMillis);
+ void registerCanvasContext(CanvasContext* context);
+ void unregisterCanvasContext(CanvasContext* context);
+ void onContextStopped(CanvasContext* context);
private:
friend class RenderThread;
- explicit CacheManager();
+ explicit CacheManager(RenderThread& thread);
+ void setupCacheLimits();
+ bool areAllContextsStopped();
+ void checkUiHidden();
+ void scheduleDestroyContext();
+ void cancelDestroyContext();
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
void reset(sk_sp<GrDirectContext> grContext);
#endif
void destroy();
- const size_t mMaxSurfaceArea;
+ RenderThread& mRenderThread;
+ const MemoryPolicy& mMemoryPolicy;
#ifdef __ANDROID__ // Layoutlib does not support hardware acceleration
sk_sp<GrDirectContext> mGrContext;
#endif
- const size_t mMaxResourceBytes;
- const size_t mBackgroundResourceBytes;
+ size_t mMaxSurfaceArea = 0;
+
+ size_t mMaxResourceBytes = 0;
+ size_t mBackgroundResourceBytes = 0;
+
+ size_t mMaxGpuFontAtlasBytes = 0;
+ size_t mMaxCpuFontCacheBytes = 0;
+ size_t mBackgroundCpuFontCacheBytes = 0;
+
+ std::vector<CanvasContext*> mCanvasContexts;
+ RingBuffer<uint64_t, 100> mFrameCompletions;
- const size_t mMaxGpuFontAtlasBytes;
- const size_t mMaxCpuFontCacheBytes;
- const size_t mBackgroundCpuFontCacheBytes;
+ nsecs_t mLastDeferredCleanup = 0;
+ bool mIsDestructionPending = false;
+ uint32_t mGenerationId = 0;
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 75d3ff7753cb..6a0c5a8e499e 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -42,9 +42,6 @@
#include "utils/GLUtils.h"
#include "utils/TimeUtils.h"
-#define TRIM_MEMORY_COMPLETE 80
-#define TRIM_MEMORY_UI_HIDDEN 20
-
#define LOG_FRAMETIME_MMA 0
#if LOG_FRAMETIME_MMA
@@ -122,6 +119,7 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode*
, mProfiler(mJankTracker.frames(), thread.timeLord().frameIntervalNanos())
, mContentDrawBounds(0, 0, 0, 0)
, mRenderPipeline(std::move(renderPipeline)) {
+ mRenderThread.cacheManager().registerCanvasContext(this);
rootRenderNode->makeRoot();
mRenderNodes.emplace_back(rootRenderNode);
mProfiler.setDensity(DeviceInfo::getDensity());
@@ -133,6 +131,7 @@ CanvasContext::~CanvasContext() {
node->clearRoot();
}
mRenderNodes.clear();
+ mRenderThread.cacheManager().unregisterCanvasContext(this);
}
void CanvasContext::addRenderNode(RenderNode* node, bool placeFront) {
@@ -154,6 +153,7 @@ void CanvasContext::destroy() {
freePrefetchedLayers();
destroyHardwareResources();
mAnimationContext->destroy();
+ mRenderThread.cacheManager().onContextStopped(this);
}
static void setBufferCount(ANativeWindow* window) {
@@ -251,6 +251,7 @@ void CanvasContext::setStopped(bool stopped) {
mGenerationID++;
mRenderThread.removeFrameCallback(this);
mRenderPipeline->onStop();
+ mRenderThread.cacheManager().onContextStopped(this);
} else if (mIsDirty && hasSurface()) {
mRenderThread.postFrameCallback(this);
}
@@ -461,7 +462,6 @@ void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t sy
}
void CanvasContext::stopDrawing() {
- cleanupResources();
mRenderThread.removeFrameCallback(this);
mAnimationContext->pauseAnimators();
mGenerationID++;
@@ -648,25 +648,10 @@ nsecs_t CanvasContext::draw() {
}
}
- cleanupResources();
mRenderThread.cacheManager().onFrameCompleted();
return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
}
-void CanvasContext::cleanupResources() {
- auto& tracker = mJankTracker.frames();
- auto size = tracker.size();
- auto capacity = tracker.capacity();
- if (size == capacity) {
- nsecs_t nowNanos = systemTime(SYSTEM_TIME_MONOTONIC);
- nsecs_t frameCompleteNanos =
- tracker[0].get(FrameInfoIndex::FrameCompleted);
- nsecs_t frameDiffNanos = nowNanos - frameCompleteNanos;
- nsecs_t cleanupMillis = ns2ms(std::max(frameDiffNanos, 10_s));
- mRenderThread.cacheManager().performDeferredCleanup(cleanupMillis);
- }
-}
-
void CanvasContext::reportMetricsWithPresentTime() {
{ // acquire lock
std::scoped_lock lock(mFrameMetricsReporterMutex);
@@ -790,6 +775,7 @@ SkISize CanvasContext::getNextFrameSize() const {
SkISize size;
size.fWidth = ANativeWindow_getWidth(anw);
size.fHeight = ANativeWindow_getHeight(anw);
+ mRenderThread.cacheManager().notifyNextFrameSize(size.fWidth, size.fHeight);
return size;
}
@@ -868,18 +854,6 @@ void CanvasContext::destroyHardwareResources() {
}
}
-void CanvasContext::trimMemory(RenderThread& thread, int level) {
- ATRACE_CALL();
- if (!thread.getGrContext()) return;
- ATRACE_CALL();
- if (level >= TRIM_MEMORY_COMPLETE) {
- thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
- thread.destroyRenderingContext();
- } else if (level >= TRIM_MEMORY_UI_HIDDEN) {
- thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
- }
-}
-
DeferredLayerUpdater* CanvasContext::createTextureLayer() {
return mRenderPipeline->createTextureLayer();
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 951ee216ce35..748ab96d7e06 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -127,6 +127,7 @@ public:
void setSurfaceControl(ASurfaceControl* surfaceControl);
bool pauseSurface();
void setStopped(bool stopped);
+ bool isStopped() { return mStopped || !hasSurface(); }
bool hasSurface() const { return mNativeSurface.get(); }
void allocateBuffers();
@@ -148,7 +149,6 @@ public:
void markLayerInUse(RenderNode* node);
void destroyHardwareResources();
- static void trimMemory(RenderThread& thread, int level);
DeferredLayerUpdater* createTextureLayer();
@@ -330,8 +330,6 @@ private:
std::function<bool(int64_t, int64_t, int64_t)> mASurfaceTransactionCallback;
std::function<void()> mPrepareSurfaceControlForWebviewCallback;
-
- void cleanupResources();
};
} /* namespace renderthread */
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 59c914f0198c..03f02de98efe 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -243,7 +243,9 @@ bool DrawFrameTask::syncFrameState(TreeInfo& info) {
mContext->unpinImages();
for (size_t i = 0; i < mLayers.size(); i++) {
- mLayers[i]->apply();
+ if (mLayers[i]) {
+ mLayers[i]->apply();
+ }
}
mLayers.clear();
mContext->setContentDrawBounds(mContentDrawBounds);
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index 40a0bac3762d..332471545f82 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -196,7 +196,8 @@ void RenderProxy::trimMemory(int level) {
// Avoid creating a RenderThread to do a trimMemory.
if (RenderThread::hasInstance()) {
RenderThread& thread = RenderThread::getInstance();
- thread.queue().post([&thread, level]() { CanvasContext::trimMemory(thread, level); });
+ const auto trimLevel = static_cast<TrimLevel>(level);
+ thread.queue().post([&thread, trimLevel]() { thread.trimMemory(trimLevel); });
}
}
@@ -205,7 +206,7 @@ void RenderProxy::purgeCaches() {
RenderThread& thread = RenderThread::getInstance();
thread.queue().post([&thread]() {
if (thread.getGrContext()) {
- thread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ thread.cacheManager().trimMemory(TrimLevel::COMPLETE);
}
});
}
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index 3ff4081726f3..7a7f1abdd268 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -251,7 +251,7 @@ void RenderThread::initThreadLocals() {
mEglManager = new EglManager();
mRenderState = new RenderState(*this);
mVkManager = VulkanManager::getInstance();
- mCacheManager = new CacheManager();
+ mCacheManager = new CacheManager(*this);
}
void RenderThread::setupFrameInterval() {
@@ -453,6 +453,8 @@ bool RenderThread::threadLoop() {
// next vsync (oops), so none of the callbacks are run.
requestVsync();
}
+
+ mCacheManager->onThreadIdle();
}
return false;
@@ -502,6 +504,11 @@ void RenderThread::preload() {
HardwareBitmapUploader::initialize();
}
+void RenderThread::trimMemory(TrimLevel level) {
+ ATRACE_CALL();
+ cacheManager().trimMemory(level);
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index c1f6790b25b2..0a89e5e944f8 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -17,11 +17,11 @@
#ifndef RENDERTHREAD_H_
#define RENDERTHREAD_H_
-#include <surface_control_private.h>
#include <GrDirectContext.h>
#include <SkBitmap.h>
#include <cutils/compiler.h>
#include <private/android/choreographer.h>
+#include <surface_control_private.h>
#include <thread/ThreadBase.h>
#include <utils/Looper.h>
#include <utils/Thread.h>
@@ -31,6 +31,7 @@
#include <set>
#include "CacheManager.h"
+#include "MemoryPolicy.h"
#include "ProfileDataContainer.h"
#include "RenderTask.h"
#include "TimeLord.h"
@@ -172,6 +173,8 @@ public:
return mASurfaceControlFunctions;
}
+ void trimMemory(TrimLevel level);
+
/**
* isCurrent provides a way to query, if the caller is running on
* the render thread.
diff --git a/libs/hwui/tests/unit/CacheManagerTests.cpp b/libs/hwui/tests/unit/CacheManagerTests.cpp
index edd3e4e4f4d4..df06ead3aa0c 100644
--- a/libs/hwui/tests/unit/CacheManagerTests.cpp
+++ b/libs/hwui/tests/unit/CacheManagerTests.cpp
@@ -58,7 +58,7 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) {
ASSERT_TRUE(SkImage_pinAsTexture(image.get(), grContext));
// attempt to trim all memory while we still hold strong refs
- renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
// free the surfaces
@@ -75,11 +75,11 @@ RENDERTHREAD_SKIA_PIPELINE_TEST(CacheManager, trimMemory) {
ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() < purgeableBytes);
// UI hidden and make sure only some got purged (unique should remain)
- renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::UiHidden);
+ renderThread.cacheManager().trimMemory(TrimLevel::UI_HIDDEN);
ASSERT_TRUE(0 < grContext->getResourceCachePurgeableBytes());
ASSERT_TRUE(renderThread.cacheManager().getBackgroundCacheSize() > getCacheUsage(grContext));
// complete and make sure all get purged
- renderThread.cacheManager().trimMemory(CacheManager::TrimMemoryMode::Complete);
+ renderThread.cacheManager().trimMemory(TrimLevel::COMPLETE);
ASSERT_TRUE(0 == grContext->getResourceCachePurgeableBytes());
}
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index be9862bf1137..49b314d8481d 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -242,13 +242,14 @@ public final class MediaFormat {
* To decode such an image, {@link MediaCodec} decoder for
* {@link #MIMETYPE_VIDEO_HEVC} shall be used. The client needs to form
* the correct {@link #MediaFormat} based on additional information in
- * the track format, and send it to {@link MediaCodec#configure}.
+ * the track format (shown in the next paragraph), and send it to
+ * {@link MediaCodec#configure}.
*
* The track's MediaFormat will come with {@link #KEY_WIDTH} and
* {@link #KEY_HEIGHT} keys, which describes the width and height
* of the image. If the image doesn't contain grid (i.e. none of
* {@link #KEY_TILE_WIDTH}, {@link #KEY_TILE_HEIGHT},
- * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLUMNS} are present}), the
+ * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLUMNS} are present), the
* track will contain a single sample of coded data for the entire image,
* and the image width and height should be used to set up the decoder.
*
@@ -266,6 +267,36 @@ public final class MediaFormat {
public static final String MIMETYPE_IMAGE_ANDROID_HEIC = "image/vnd.android.heic";
/**
+ * MIME type for AVIF still image data encoded in AV1.
+ *
+ * To decode such an image, {@link MediaCodec} decoder for
+ * {@link #MIMETYPE_VIDEO_AV1} shall be used. The client needs to form
+ * the correct {@link #MediaFormat} based on additional information in
+ * the track format (shown in the next paragraph), and send it to
+ * {@link MediaCodec#configure}.
+ *
+ * The track's MediaFormat will come with {@link #KEY_WIDTH} and
+ * {@link #KEY_HEIGHT} keys, which describes the width and height
+ * of the image. If the image doesn't contain grid (i.e. none of
+ * {@link #KEY_TILE_WIDTH}, {@link #KEY_TILE_HEIGHT},
+ * {@link #KEY_GRID_ROWS}, {@link #KEY_GRID_COLUMNS} are present), the
+ * track will contain a single sample of coded data for the entire image,
+ * and the image width and height should be used to set up the decoder.
+ *
+ * If the image does come with grid, each sample from the track will
+ * contain one tile in the grid, of which the size is described by
+ * {@link #KEY_TILE_WIDTH} and {@link #KEY_TILE_HEIGHT}. This size
+ * (instead of {@link #KEY_WIDTH} and {@link #KEY_HEIGHT}) should be
+ * used to set up the decoder. The track contains {@link #KEY_GRID_ROWS}
+ * by {@link #KEY_GRID_COLUMNS} samples in row-major, top-row first,
+ * left-to-right order. The output image should be reconstructed by
+ * first tiling the decoding results of the tiles in the correct order,
+ * then trimming (before rotation is applied) on the bottom and right
+ * side, if the tiled area is larger than the image width and height.
+ */
+ public static final String MIMETYPE_IMAGE_AVIF = "image/avif";
+
+ /**
* MIME type for WebVTT subtitle data.
*/
public static final String MIMETYPE_TEXT_VTT = "text/vtt";
@@ -485,9 +516,11 @@ public final class MediaFormat {
/**
* A key describing the width (in pixels) of each tile of the content in a
- * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track.
+ * The associated value is an integer.
*
- * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} on decoding
+ * instructions of such tracks.
*
* @see #KEY_TILE_HEIGHT
* @see #KEY_GRID_ROWS
@@ -497,9 +530,11 @@ public final class MediaFormat {
/**
* A key describing the height (in pixels) of each tile of the content in a
- * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track.
+ * The associated value is an integer.
*
- * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} on decoding
+ * instructions of such tracks.
*
* @see #KEY_TILE_WIDTH
* @see #KEY_GRID_ROWS
@@ -509,9 +544,11 @@ public final class MediaFormat {
/**
* A key describing the number of grid rows in the content in a
- * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track.
+ * The associated value is an integer.
*
- * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} on decoding
+ * instructions of such tracks.
*
* @see #KEY_TILE_WIDTH
* @see #KEY_TILE_HEIGHT
@@ -521,9 +558,11 @@ public final class MediaFormat {
/**
* A key describing the number of grid columns in the content in a
- * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track. The associated value is an integer.
+ * {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track.
+ * The associated value is an integer.
*
- * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} on decoding instructions of such tracks.
+ * Refer to {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} on decoding
+ * instructions of such tracks.
*
* @see #KEY_TILE_WIDTH
* @see #KEY_TILE_HEIGHT
@@ -1350,9 +1389,9 @@ public final class MediaFormat {
* selected in the absence of a specific user choice.
* This is currently used in two scenarios:
* 1) for subtitle tracks, when the user selected 'Default' for the captioning locale.
- * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the
- * primary item in the file.
-
+ * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} / {@link #MIMETYPE_IMAGE_AVIF} track,
+ * indicating the image is the primary item in the file.
+ *
* The associated value is an integer, where non-0 means TRUE. This is an optional
* field; if not specified, DEFAULT is considered to be FALSE.
*/
diff --git a/media/java/android/media/projection/MediaProjectionGlobal.java b/media/java/android/media/projection/MediaProjectionGlobal.java
deleted file mode 100644
index 4374a052dad2..000000000000
--- a/media/java/android/media/projection/MediaProjectionGlobal.java
+++ /dev/null
@@ -1,120 +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 android.media.projection;
-
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.SystemApi;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.hardware.display.DisplayManagerGlobal;
-import android.hardware.display.IDisplayManager;
-import android.hardware.display.VirtualDisplay;
-import android.hardware.display.VirtualDisplayConfig;
-import android.os.IBinder;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.view.Surface;
-
-/**
- * This is a helper for MediaProjection when requests are made from outside an application. This
- * should only be used by processes running as shell as a way to capture recordings without being
- * an application. The requests will fail if coming from any process that's not Shell.
- * @hide
- */
-@SystemApi
-public class MediaProjectionGlobal {
- private static final Object sLock = new Object();
- private static MediaProjectionGlobal sInstance;
-
- /**
- * @return The instance of {@link MediaProjectionGlobal}
- */
- @NonNull
- public static MediaProjectionGlobal getInstance() {
- synchronized (sLock) {
- if (sInstance == null) {
- final IBinder displayBinder = ServiceManager.getService(Context.DISPLAY_SERVICE);
- final IBinder packageBinder = ServiceManager.getService("package");
- if (displayBinder != null && packageBinder != null) {
- sInstance = new MediaProjectionGlobal(
- IDisplayManager.Stub.asInterface(displayBinder),
- IPackageManager.Stub.asInterface(packageBinder));
- }
- }
- return sInstance;
- }
- }
-
- private final IDisplayManager mDm;
- private final IPackageManager mPackageManager;
-
- private MediaProjectionGlobal(IDisplayManager dm, IPackageManager packageManager) {
- mDm = dm;
- mPackageManager = packageManager;
- }
-
- /**
- * Creates a VirtualDisplay that will mirror the content of displayIdToMirror
- * @param name The name for the virtual display
- * @param width The initial width for the virtual display
- * @param height The initial height for the virtual display
- * @param displayIdToMirror The displayId that will be mirrored into the virtual display.
- * @return VirtualDisplay that can be used to update properties.
- */
- @Nullable
- public VirtualDisplay createVirtualDisplay(@NonNull String name, int width, int height,
- int displayIdToMirror, @Nullable Surface surface) {
-
- // Density doesn't matter since this virtual display is only used for mirroring.
- VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
- height, 1 /* densityDpi */)
- .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
- .setDisplayIdToMirror(displayIdToMirror);
- if (surface != null) {
- builder.setSurface(surface);
- }
- VirtualDisplayConfig virtualDisplayConfig = builder.build();
-
- String[] packages;
- try {
- packages = mPackageManager.getPackagesForUid(Process.myUid());
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
-
- // Just use the first one since it just needs to match the package when looking it up by
- // calling UID in system server.
- // The call may come from a rooted device, in that case the requesting uid will be root so
- // it will not have any package name
- String packageName = packages == null ? null : packages[0];
- DisplayManagerGlobal.VirtualDisplayCallback
- callbackWrapper = new DisplayManagerGlobal.VirtualDisplayCallback(null, null);
- int displayId;
- try {
- displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper, null,
- packageName);
- } catch (RemoteException ex) {
- throw ex.rethrowFromSystemServer();
- }
- return DisplayManagerGlobal.getInstance().createVirtualDisplayWrapper(virtualDisplayConfig,
- null, callbackWrapper, displayId);
- }
-}
diff --git a/media/native/midi/Android.bp b/media/native/midi/Android.bp
index 7acb8c744ba7..a991a71fd3f1 100644
--- a/media/native/midi/Android.bp
+++ b/media/native/midi/Android.bp
@@ -74,4 +74,8 @@ ndk_library {
symbol_file: "libamidi.map.txt",
first_version: "29",
+ export_header_libs: [
+ "amidi",
+ ],
+
}
diff --git a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
index 2fe7b1668d08..08a447f45a30 100644
--- a/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
+++ b/media/packages/BluetoothMidiService/src/com/android/bluetoothmidiservice/BluetoothMidiDevice.java
@@ -138,8 +138,6 @@ public final class BluetoothMidiDevice {
// switch to receiving notifications
mBluetoothGatt.readCharacteristic(characteristic);
}
-
- openBluetoothDevice(mBluetoothDevice);
}
} else {
Log.e(TAG, "onServicesDiscovered received: " + status);
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 32b7a0780e63..8594ba5ca2da 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -27,6 +27,9 @@ ndk_library {
symbol_file: "libandroid.map.txt",
first_version: "9",
unversioned_until: "current",
+ export_header_libs: [
+ "libandroid_headers",
+ ],
}
cc_defaults {
diff --git a/packages/SettingsLib/Spa/TEST_MAPPING b/packages/SettingsLib/Spa/TEST_MAPPING
index ef3db4aef66d..b4b65d4c0ddc 100644
--- a/packages/SettingsLib/Spa/TEST_MAPPING
+++ b/packages/SettingsLib/Spa/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "SpaLibTests"
+ },
+ {
+ "name": "SpaPrivilegedLibTests"
}
]
}
diff --git a/packages/SettingsLib/Spa/build.gradle b/packages/SettingsLib/Spa/build.gradle
index f8667edb7e8c..6384cad7d620 100644
--- a/packages/SettingsLib/Spa/build.gradle
+++ b/packages/SettingsLib/Spa/build.gradle
@@ -22,7 +22,7 @@ buildscript {
}
}
plugins {
- id 'com.android.application' version '7.3.0-rc01' apply false
- id 'com.android.library' version '7.3.0-rc01' apply false
+ id 'com.android.application' version '7.3.0' apply false
+ id 'com.android.library' version '7.3.0' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
index 1ebc5da8c593..332d5a836c9f 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryDebugActivity.kt
@@ -18,8 +18,4 @@ package com.android.settingslib.spa.gallery
import com.android.settingslib.spa.framework.DebugActivity
-class GalleryDebugActivity : DebugActivity(
- SpaEnvironment.EntryRepository,
- browseActivityClass = MainActivity::class.java,
- entryProviderAuthorities = "com.android.spa.gallery.provider",
-)
+class GalleryDebugActivity : DebugActivity(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
index d3e0096887e6..5e048612292d 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryEntryProvider.kt
@@ -18,7 +18,4 @@ package com.android.settingslib.spa.gallery
import com.android.settingslib.spa.framework.EntryProvider
-class GalleryEntryProvider : EntryProvider(
- SpaEnvironment.EntryRepository,
- browseActivityClass = MainActivity::class.java,
-)
+class GalleryEntryProvider : EntryProvider(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 6fe88e1fd37c..33c4d7712753 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -18,9 +18,9 @@ package com.android.settingslib.spa.gallery
import android.os.Bundle
import androidx.navigation.NamedNavArgument
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
import com.android.settingslib.spa.framework.common.SettingsPage
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
import com.android.settingslib.spa.gallery.home.HomePageProvider
import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
@@ -62,9 +62,8 @@ fun createSettingsPage(
)
}
-object SpaEnvironment {
- val PageProviderRepository: SettingsPageProviderRepository by
- lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
+object GallerySpaEnvironment : SpaEnvironment() {
+ override val pageProviderRepository = lazy {
SettingsPageProviderRepository(
allPageProviders = listOf(
HomePageProvider,
@@ -88,9 +87,7 @@ object SpaEnvironment {
)
}
- val EntryRepository: SettingsEntryRepository by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
- SettingsEntryRepository(PageProviderRepository)
- }
+ override val browseActivityClass = MainActivity::class.java
- // TODO: add other environment setup here.
+ override val entryProviderAuthorities = "com.android.spa.gallery.provider"
}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
index a06384762372..5e859ce778cf 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/MainActivity.kt
@@ -18,4 +18,4 @@ package com.android.settingslib.spa.gallery
import com.android.settingslib.spa.framework.BrowseActivity
-class MainActivity : BrowseActivity(SpaEnvironment.PageProviderRepository)
+class MainActivity : BrowseActivity(GallerySpaEnvironment)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index 0bb631acf7fc..138ea02b04d1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -31,7 +31,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.android.settingslib.spa.R
-import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
+import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.compose.localNavController
import com.android.settingslib.spa.framework.theme.SettingsTheme
import com.android.settingslib.spa.framework.util.navRoute
@@ -48,9 +48,9 @@ const val NULL_PAGE_NAME = "NULL"
* $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination HOME
* $ adb shell am start -n <BrowseActivityComponent> -e spa:SpaActivity:destination ARGUMENT/bar/5
*/
-open class BrowseActivity(
- private val sppRepository: SettingsPageProviderRepository,
-) : ComponentActivity() {
+open class BrowseActivity(spaEnvironment: SpaEnvironment) : ComponentActivity() {
+ private val sppRepository by spaEnvironment.pageProviderRepository
+
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_SpaLib_DayNight)
super.onCreate(savedInstanceState)
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
index c698d9c7ca9a..85fc366dd3f5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/DebugActivity.kt
@@ -35,8 +35,8 @@ import androidx.navigation.navArgument
import com.android.settingslib.spa.R
import com.android.settingslib.spa.framework.BrowseActivity.Companion.KEY_DESTINATION
import com.android.settingslib.spa.framework.common.SettingsEntry
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironment
import com.android.settingslib.spa.framework.compose.localNavController
import com.android.settingslib.spa.framework.compose.navigator
import com.android.settingslib.spa.framework.compose.toState
@@ -61,11 +61,9 @@ private const val PARAM_NAME_ENTRY_ID = "eid"
* For gallery, Activity = com.android.settingslib.spa.gallery/.GalleryDebugActivity
* For SettingsGoogle, Activity = com.android.settings/.spa.SpaDebugActivity
*/
-open class DebugActivity(
- private val entryRepository: SettingsEntryRepository,
- private val browseActivityClass: Class<*>,
- private val entryProviderAuthorities: String? = null,
-) : ComponentActivity() {
+open class DebugActivity(private val spaEnvironment: SpaEnvironment) : ComponentActivity() {
+ private val entryRepository by spaEnvironment.entryRepository
+
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_SpaLib_DayNight)
super.onCreate(savedInstanceState)
@@ -79,7 +77,7 @@ open class DebugActivity(
}
private fun displayDebugMessage() {
- if (entryProviderAuthorities == null) return
+ val entryProviderAuthorities = spaEnvironment.entryProviderAuthorities ?: return
try {
val query = EntryProvider.QueryEnum.PAGE_INFO_QUERY
@@ -216,7 +214,7 @@ open class DebugActivity(
if (page.hasRuntimeParam()) return null
val context = LocalContext.current
val route = page.buildRoute()
- val intent = Intent(context, browseActivityClass).apply {
+ val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
putExtra(KEY_DESTINATION, route)
}
return {
@@ -230,7 +228,7 @@ open class DebugActivity(
if (entry.hasRuntimeParam()) return null
val context = LocalContext.current
val route = entry.buildRoute()
- val intent = Intent(context, browseActivityClass).apply {
+ val intent = Intent(context, spaEnvironment.browseActivityClass).apply {
putExtra(KEY_DESTINATION, route)
}
return {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
index d923c1ce83e8..f0ec83b42498 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/EntryProvider.kt
@@ -28,8 +28,8 @@ import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
import android.util.Log
-import com.android.settingslib.spa.framework.common.SettingsEntryRepository
import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SpaEnvironment
/**
* The content provider to return entry related data, which can be used for search and hierarchy.
@@ -42,10 +42,9 @@ import com.android.settingslib.spa.framework.common.SettingsPage
* $ adb shell content query --uri content://<AuthorityPath>/page_info
* $ adb shell content query --uri content://<AuthorityPath>/entry_info
*/
-open class EntryProvider(
- private val entryRepository: SettingsEntryRepository,
- private val browseActivityClass: Class<*>? = null,
-) : ContentProvider() {
+open class EntryProvider(spaEnvironment: SpaEnvironment) : ContentProvider() {
+ private val entryRepository by spaEnvironment.entryRepository
+ private val browseActivityClass = spaEnvironment.browseActivityClass
/**
* Enum to define all column names in provider.
@@ -220,7 +219,7 @@ open class EntryProvider(
}
private fun createBrowsePageIntent(page: SettingsPage): Intent {
- if (context == null || browseActivityClass == null || page.hasRuntimeParam())
+ if (context == null || page.hasRuntimeParam())
return Intent()
return Intent().setComponent(ComponentName(context!!, browseActivityClass)).apply {
@@ -231,8 +230,7 @@ open class EntryProvider(
private fun createBrowsePageAdbCommand(page: SettingsPage): String? {
if (context == null || page.hasRuntimeParam()) return null
val packageName = context!!.packageName
- val activityName =
- browseActivityClass?.name?.replace(packageName, "") ?: "<browse-activity-class>"
+ val activityName = browseActivityClass.name.replace(packageName, "")
return "adb shell am start -n $packageName/$activityName" +
" -e ${BrowseActivity.KEY_DESTINATION} ${page.buildRoute()}"
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
new file mode 100644
index 000000000000..111555b468c1
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.common
+
+import android.app.Activity
+
+abstract class SpaEnvironment {
+ abstract val pageProviderRepository: Lazy<SettingsPageProviderRepository>
+
+ val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
+
+ abstract val browseActivityClass: Class<out Activity>
+
+ open val entryProviderAuthorities: String? = null
+
+ // TODO: add other environment setup here.
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
index b8e43609708d..6a88f2dd369c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/Actions.kt
@@ -16,12 +16,18 @@
package com.android.settingslib.spa.widget.scaffold
+import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowBack
import androidx.compose.material.icons.outlined.MoreVert
+import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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.res.stringResource
import com.android.settingslib.spa.framework.compose.LocalNavController
@@ -47,7 +53,21 @@ private fun BackAction(contentDescription: String, onClick: () -> Unit) {
}
@Composable
-fun MoreOptionsAction(onClick: () -> Unit) {
+fun MoreOptionsAction(
+ content: @Composable ColumnScope.(onDismissRequest: () -> Unit) -> Unit,
+) {
+ var expanded by rememberSaveable { mutableStateOf(false) }
+ MoreOptionsActionButton { expanded = true }
+ val onDismissRequest = { expanded = false }
+ DropdownMenu(
+ expanded = expanded,
+ onDismissRequest = onDismissRequest,
+ content = { content(onDismissRequest) },
+ )
+}
+
+@Composable
+private fun MoreOptionsActionButton(onClick: () -> Unit) {
IconButton(onClick) {
Icon(
imageVector = Icons.Outlined.MoreVert,
diff --git a/packages/SettingsLib/SpaPrivileged/Android.bp b/packages/SettingsLib/SpaPrivileged/Android.bp
index 082ce97e3273..e7e37e418185 100644
--- a/packages/SettingsLib/SpaPrivileged/Android.bp
+++ b/packages/SettingsLib/SpaPrivileged/Android.bp
@@ -45,3 +45,9 @@ java_defaults {
"-J-Xmx4G",
],
}
+
+// Expose the srcs to tests, so the tests can access the internal classes.
+filegroup {
+ name: "SpaPrivilegedLib_srcs",
+ srcs: ["src/**/*.kt"],
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
new file mode 100644
index 000000000000..7796549485b6
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -0,0 +1,11 @@
+package com.android.settingslib.spaprivileged.framework.common
+
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.os.UserManager
+
+/** The [UserManager] instance. */
+val Context.userManager get() = getSystemService(UserManager::class.java)!!
+
+/** The [DevicePolicyManager] instance. */
+val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!!
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
index bb94b33bfa28..ee8900352cf2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppsRepository.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt
@@ -21,7 +21,6 @@ import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
-import android.content.pm.UserInfo
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
@@ -30,14 +29,25 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
-class AppsRepository(context: Context) {
+/**
+ * The config used to load the App List.
+ */
+internal data class AppListConfig(
+ val userId: Int,
+ val showInstantApps: Boolean,
+)
+
+/**
+ * The repository to load the App List data.
+ */
+internal class AppListRepository(context: Context) {
private val packageManager = context.packageManager
- fun loadApps(userInfoFlow: Flow<UserInfo>): Flow<List<ApplicationInfo>> = userInfoFlow
+ fun loadApps(configFlow: Flow<AppListConfig>): Flow<List<ApplicationInfo>> = configFlow
.map { loadApps(it) }
.flowOn(Dispatchers.Default)
- private suspend fun loadApps(userInfo: UserInfo): List<ApplicationInfo> {
+ private suspend fun loadApps(config: AppListConfig): List<ApplicationInfo> {
return coroutineScope {
val hiddenSystemModulesDeferred = async {
packageManager.getInstalledModules(0)
@@ -50,11 +60,11 @@ class AppsRepository(context: Context) {
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong()
)
val installedApplicationsAsUser =
- packageManager.getInstalledApplicationsAsUser(flags, userInfo.id)
+ packageManager.getInstalledApplicationsAsUser(flags, config.userId)
val hiddenSystemModules = hiddenSystemModulesDeferred.await()
installedApplicationsAsUser.filter { app ->
- app.isInAppList(hiddenSystemModules)
+ app.isInAppList(config.showInstantApps, hiddenSystemModules)
}
}
}
@@ -63,9 +73,7 @@ class AppsRepository(context: Context) {
userIdFlow: Flow<Int>,
showSystemFlow: Flow<Boolean>,
): Flow<(app: ApplicationInfo) -> Boolean> =
- userIdFlow.combine(showSystemFlow) { userId, showSystem ->
- showSystemPredicate(userId, showSystem)
- }
+ userIdFlow.combine(showSystemFlow, ::showSystemPredicate)
private suspend fun showSystemPredicate(
userId: Int,
@@ -102,12 +110,15 @@ class AppsRepository(context: Context) {
}
companion object {
- private fun ApplicationInfo.isInAppList(hiddenSystemModules: Set<String>) =
- when {
- packageName in hiddenSystemModules -> false
- enabled -> true
- enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER -> true
- else -> false
- }
+ private fun ApplicationInfo.isInAppList(
+ showInstantApps: Boolean,
+ hiddenSystemModules: Set<String>,
+ ) = when {
+ !showInstantApps && isInstantApp -> false
+ packageName in hiddenSystemModules -> false
+ enabled -> true
+ isDisabledUntilUsed -> true
+ else -> false
+ }
}
}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
index 9265158b3b4a..1e487daa36fb 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt
@@ -18,7 +18,6 @@ package com.android.settingslib.spaprivileged.model.app
import android.app.Application
import android.content.pm.ApplicationInfo
-import android.content.pm.UserInfo
import android.icu.text.Collator
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
@@ -48,28 +47,29 @@ internal data class AppListData<T : AppRecord>(
internal class AppListViewModel<T : AppRecord>(
application: Application,
) : AndroidViewModel(application) {
- val userInfo = StateFlowBridge<UserInfo>()
+ val appListConfig = StateFlowBridge<AppListConfig>()
val listModel = StateFlowBridge<AppListModel<T>>()
val showSystem = StateFlowBridge<Boolean>()
val option = StateFlowBridge<Int>()
val searchQuery = StateFlowBridge<String>()
- private val appsRepository = AppsRepository(application)
+ private val appListRepository = AppListRepository(application)
private val appRepository = AppRepositoryImpl(application)
private val collator = Collator.getInstance().freeze()
private val labelMap = ConcurrentHashMap<String, String>()
private val scope = viewModelScope + Dispatchers.Default
- private val userIdFlow = userInfo.flow.map { it.id }
+ private val userIdFlow = appListConfig.flow.map { it.userId }
private val recordListFlow = listModel.flow
- .flatMapLatest { it.transform(userIdFlow, appsRepository.loadApps(userInfo.flow)) }
+ .flatMapLatest { it.transform(userIdFlow, appListRepository.loadApps(appListConfig.flow)) }
.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1)
- private val systemFilteredFlow = appsRepository.showSystemPredicate(userIdFlow, showSystem.flow)
- .combine(recordListFlow) { showAppPredicate, recordList ->
- recordList.filter { showAppPredicate(it.app) }
- }
+ private val systemFilteredFlow =
+ appListRepository.showSystemPredicate(userIdFlow, showSystem.flow)
+ .combine(recordListFlow) { showAppPredicate, recordList ->
+ recordList.filter { showAppPredicate(it.app) }
+ }
val appListDataFlow = option.flow.flatMapLatest(::filterAndSort)
.combine(searchQuery.flow) { appListData, searchQuery ->
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
index 96e3e77e9322..c1ac5d473dba 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/Apps.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
@@ -16,9 +16,13 @@
package com.android.settingslib.spaprivileged.model.app
+import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.UserHandle
+import android.os.UserManager
+import com.android.settingslib.spaprivileged.framework.common.devicePolicyManager
+import com.android.settingslib.spaprivileged.framework.common.userManager
/** The user id for a given application. */
val ApplicationInfo.userId: Int
@@ -32,8 +36,16 @@ val ApplicationInfo.userHandle: UserHandle
fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0
/** Checks whether the application is disabled until used. */
-fun ApplicationInfo.isDisabledUntilUsed(): Boolean =
- enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+val ApplicationInfo.isDisabledUntilUsed: Boolean
+ get() = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+
+/** Checks whether the application is disallowed control. */
+fun ApplicationInfo.isDisallowControl(context: Context) =
+ context.userManager.hasBaseUserRestriction(UserManager.DISALLOW_APPS_CONTROL, userHandle)
+
+/** Checks whether the application is an active admin. */
+fun ApplicationInfo.isActiveAdmin(context: Context): Boolean =
+ context.devicePolicyManager.packageHasActiveAdmins(packageName, userId)
/** Converts to the route string which used in navigation. */
fun ApplicationInfo.toRoute() = "$packageName/$userId"
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
index 5c5caf598717..215d22cd0c5e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagers.kt
@@ -32,9 +32,14 @@ object PackageManagers {
fun getPackageInfoAsUser(packageName: String, userId: Int): PackageInfo? =
getPackageInfoAsUser(packageName, 0, userId)
- fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo =
+ fun getApplicationInfoAsUser(packageName: String, userId: Int): ApplicationInfo? =
PackageManager.getApplicationInfoAsUserCached(packageName, 0, userId)
+ /** Checks whether a package is installed for a given user. */
+ fun isPackageInstalledAsUser(packageName: String, userId: Int): Boolean =
+ getApplicationInfoAsUser(packageName, userId)?.hasFlag(ApplicationInfo.FLAG_INSTALLED)
+ ?: false
+
fun ApplicationInfo.hasRequestPermission(permission: String): Boolean {
val packageInfo = getPackageInfoAsUser(packageName, PackageManager.GET_PERMISSIONS, userId)
return packageInfo?.requestedPermissions?.let {
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
index c89ffe560eb7..9611b136d881 100644
--- 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
@@ -53,15 +53,24 @@ class AppInfoProvider(private val packageInfo: PackageInfo) {
),
horizontalAlignment = Alignment.CenterHorizontally,
) {
+ val app = packageInfo.applicationInfo
Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) {
- AppIcon(app = packageInfo.applicationInfo, size = SettingsDimension.appIconInfoSize)
+ AppIcon(app = app, size = SettingsDimension.appIconInfoSize)
}
- AppLabel(packageInfo.applicationInfo)
+ AppLabel(app)
+ InstallType(app)
if (displayVersion) AppVersion()
}
}
@Composable
+ private fun InstallType(app: ApplicationInfo) {
+ if (!app.isInstantApp) return
+ Spacer(modifier = Modifier.height(4.dp))
+ SettingsBody(stringResource(R.string.install_type_instant))
+ }
+
+ @Composable
private fun AppVersion() {
if (packageInfo.versionName == null) return
Spacer(modifier = Modifier.height(4.dp))
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 315dc5d6ff70..6318b4e9c186 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -16,7 +16,6 @@
package com.android.settingslib.spaprivileged.template.app
-import android.content.pm.UserInfo
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
@@ -33,6 +32,7 @@ import com.android.settingslib.spa.framework.compose.toState
import com.android.settingslib.spa.framework.theme.SettingsDimension
import com.android.settingslib.spa.widget.ui.PlaceholderTitle
import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListData
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppListViewModel
@@ -41,17 +41,22 @@ import kotlinx.coroutines.Dispatchers
private const val TAG = "AppList"
+/**
+ * The template to render an App List.
+ *
+ * This UI element will take the remaining space on the screen to show the App List.
+ */
@Composable
internal fun <T : AppRecord> AppList(
- userInfo: UserInfo,
+ appListConfig: AppListConfig,
listModel: AppListModel<T>,
showSystem: State<Boolean>,
option: State<Int>,
searchQuery: State<String>,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
) {
- LogCompositions(TAG, userInfo.id.toString())
- val appListData = loadAppEntries(userInfo, listModel, showSystem, option, searchQuery)
+ LogCompositions(TAG, appListConfig.userId.toString())
+ val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
AppListWidget(appListData, listModel, appItem)
}
@@ -85,14 +90,14 @@ private fun <T : AppRecord> AppListWidget(
@Composable
private fun <T : AppRecord> loadAppEntries(
- userInfo: UserInfo,
+ appListConfig: AppListConfig,
listModel: AppListModel<T>,
showSystem: State<Boolean>,
option: State<Int>,
searchQuery: State<String>,
): State<AppListData<T>?> {
- val viewModel: AppListViewModel<T> = viewModel(key = userInfo.id.toString())
- viewModel.userInfo.setIfAbsent(userInfo)
+ val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
+ viewModel.appListConfig.setIfAbsent(appListConfig)
viewModel.listModel.setIfAbsent(listModel)
viewModel.showSystem.Sync(showSystem)
viewModel.option.Sync(option)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index d537ec258cad..2be1d1c6cce6 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -20,15 +20,12 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-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.res.stringResource
import com.android.settingslib.spa.framework.compose.stateOf
@@ -36,14 +33,19 @@ import com.android.settingslib.spa.widget.scaffold.MoreOptionsAction
import com.android.settingslib.spa.widget.scaffold.SettingsScaffold
import com.android.settingslib.spa.widget.ui.Spinner
import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
import com.android.settingslib.spaprivileged.model.app.AppListModel
import com.android.settingslib.spaprivileged.model.app.AppRecord
import com.android.settingslib.spaprivileged.template.common.WorkProfilePager
+/**
+ * The full screen template for an App List page.
+ */
@Composable
fun <T : AppRecord> AppListPage(
title: String,
listModel: AppListModel<T>,
+ showInstantApps: Boolean = false,
primaryUserOnly: Boolean = false,
appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
) {
@@ -62,7 +64,10 @@ fun <T : AppRecord> AppListPage(
val selectedOption = rememberSaveable { mutableStateOf(0) }
Spinner(options, selectedOption.value) { selectedOption.value = it }
AppList(
- userInfo = userInfo,
+ appListConfig = AppListConfig(
+ userId = userInfo.id,
+ showInstantApps = showInstantApps,
+ ),
listModel = listModel,
showSystem = showSystem,
option = selectedOption,
@@ -76,17 +81,12 @@ fun <T : AppRecord> AppListPage(
@Composable
private fun ShowSystemAction(showSystem: Boolean, setShowSystem: (showSystem: Boolean) -> Unit) {
- var expanded by remember { mutableStateOf(false) }
- MoreOptionsAction { expanded = true }
- DropdownMenu(
- expanded = expanded,
- onDismissRequest = { expanded = false },
- ) {
+ MoreOptionsAction { onDismissRequest ->
val menuText = if (showSystem) R.string.menu_hide_system else R.string.menu_show_system
DropdownMenuItem(
text = { Text(stringResource(menuText)) },
onClick = {
- expanded = false
+ onDismissRequest()
setShowSystem(!showSystem)
},
)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 883eddff8054..1bbc47d604d3 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -128,7 +128,7 @@ private fun TogglePermissionAppInfoPage(
userId = userId,
footerText = stringResource(listModel.footerResId),
) {
- val model = createSwitchModel(listModel, packageName, userId)
+ val model = createSwitchModel(listModel, packageName, userId) ?: return@AppInfoPage
LaunchedEffect(model, Dispatchers.Default) {
model.initState()
}
@@ -141,9 +141,9 @@ private fun <T : AppRecord> createSwitchModel(
listModel: TogglePermissionAppListModel<T>,
packageName: String,
userId: Int,
-): TogglePermissionSwitchModel<T> {
+): TogglePermissionSwitchModel<T>? {
val record = remember {
- val app = PackageManagers.getApplicationInfoAsUser(packageName, userId)
+ val app = PackageManagers.getApplicationInfoAsUser(packageName, userId) ?: return null
listModel.transformItem(app)
}
val context = LocalContext.current
diff --git a/packages/SettingsLib/SpaPrivileged/tests/Android.bp b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
new file mode 100644
index 000000000000..940a1fed817b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/Android.bp
@@ -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 {
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+ name: "SpaPrivilegedLibTests",
+ certificate: "platform",
+ platform_apis: true,
+ test_suites: ["device-tests"],
+
+ srcs: [
+ ":SpaPrivilegedLib_srcs",
+ "src/**/*.kt",
+ ],
+
+ static_libs: [
+ "SpaPrivilegedLib",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui-test-junit4",
+ "androidx.compose.ui_ui-test-manifest",
+ "androidx.test.ext.junit",
+ "androidx.test.runner",
+ "mockito-target-minus-junit4",
+ "truth-prebuilt",
+ ],
+ kotlincflags: [
+ "-Xjvm-default=all",
+ "-Xopt-in=kotlin.RequiresOptIn",
+ ],
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.xml
new file mode 100644
index 000000000000..c4f490ed398b
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/AndroidManifest.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.spaprivileged.tests">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="Tests for SpaPrivilegedLib"
+ android:targetPackage="com.android.settingslib.spaprivileged.tests" />
+</manifest>
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
new file mode 100644
index 000000000000..c010c68f37a4
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt
@@ -0,0 +1,113 @@
+/*
+ * 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.model.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.content.pm.PackageManager.ResolveInfoFlags
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+private const val USER_ID = 0
+
+@RunWith(AndroidJUnit4::class)
+class AppListRepositoryTest {
+
+ @JvmField
+ @Rule
+ val mockito: MockitoRule = MockitoJUnit.rule()
+
+ @Mock
+ private lateinit var context: Context
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ private lateinit var repository: AppListRepository
+
+ private val normalApp = ApplicationInfo().apply {
+ packageName = "normal"
+ enabled = true
+ }
+
+ private val instantApp = ApplicationInfo().apply {
+ packageName = "instant"
+ enabled = true
+ privateFlags = ApplicationInfo.PRIVATE_FLAG_INSTANT
+ }
+
+ @Before
+ fun setUp() {
+ whenever(context.packageManager).thenReturn(packageManager)
+ whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList())
+ whenever(
+ packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID))
+ ).thenReturn(listOf(normalApp, instantApp))
+ whenever(
+ packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID))
+ ).thenReturn(emptyList())
+
+ repository = AppListRepository(context)
+ }
+
+ @Test
+ fun notShowInstantApps(): Unit = runBlocking {
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = false)
+
+ val appListFlow = repository.loadApps(flowOf(appListConfig))
+
+ launch {
+ val flowValues = mutableListOf<List<ApplicationInfo>>()
+ appListFlow.toList(flowValues)
+ assertThat(flowValues).hasSize(1)
+
+ assertThat(flowValues[0]).containsExactly(normalApp)
+ }
+ }
+
+ @Test
+ fun showInstantApps(): Unit = runBlocking {
+ val appListConfig = AppListConfig(userId = USER_ID, showInstantApps = true)
+
+ val appListFlow = repository.loadApps(flowOf(appListConfig))
+
+ launch {
+ val flowValues = mutableListOf<List<ApplicationInfo>>()
+ appListFlow.toList(flowValues)
+ assertThat(flowValues).hasSize(1)
+
+ assertThat(flowValues[0]).containsExactly(normalApp, instantApp)
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 7927c5d460a4..eb53ea1d44f7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -758,16 +758,23 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
}
public boolean isBusy() {
- synchronized (mProfileLock) {
- for (LocalBluetoothProfile profile : mProfiles) {
- int status = getProfileConnectionState(profile);
- if (status == BluetoothProfile.STATE_CONNECTING
- || status == BluetoothProfile.STATE_DISCONNECTING) {
- return true;
- }
+ for (CachedBluetoothDevice memberDevice : getMemberDevice()) {
+ if (isBusyState(memberDevice)) {
+ return true;
+ }
+ }
+ return isBusyState(this);
+ }
+
+ private boolean isBusyState(CachedBluetoothDevice device){
+ for (LocalBluetoothProfile profile : device.getProfiles()) {
+ int status = device.getProfileConnectionState(profile);
+ if (status == BluetoothProfile.STATE_CONNECTING
+ || status == BluetoothProfile.STATE_DISCONNECTING) {
+ return true;
}
- return getBondState() == BluetoothDevice.BOND_BONDING;
}
+ return device.getBondState() == BluetoothDevice.BOND_BONDING;
}
private boolean updateProfiles() {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 79e99387b2fa..315ab0aac878 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -1069,4 +1069,80 @@ public class CachedBluetoothDeviceTest {
assertThat(mSubCachedDevice.mDevice).isEqualTo(mDevice);
assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
}
+
+ @Test
+ public void isBusy_mainDeviceIsConnecting_returnsBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isTrue();
+ }
+
+ @Test
+ public void isBusy_mainDeviceIsBonding_returnsBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isTrue();
+ }
+
+ @Test
+ public void isBusy_memberDeviceIsConnecting_returnsBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTING);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isTrue();
+ }
+
+ @Test
+ public void isBusy_memberDeviceIsBonding_returnsBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDING);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isTrue();
+ }
+
+ @Test
+ public void isBusy_allDevicesAreNotBusy_returnsNotBusy() {
+ mCachedDevice.addMemberDevice(mSubCachedDevice);
+ updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ updateSubDeviceProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+ when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+ when(mSubDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+
+ assertThat(mCachedDevice.getMemberDevice().contains(mSubCachedDevice)).isTrue();
+ assertThat(mCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mSubCachedDevice.getProfiles().contains(mA2dpProfile)).isTrue();
+ assertThat(mCachedDevice.isBusy()).isFalse();
+ }
}
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index 234ef241f538..aaee42fe05f0 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -1,25 +1,6 @@
{
// Looking for unit test presubmit configuration?
// This currently lives in ATP config apct/system_ui/unit_test
- "presubmit-large": [
- {
- "name": "PlatformScenarioTests",
- "options": [
- {
- "include-filter": "android.platform.test.scenario.sysui"
- },
- {
- "include-annotation": "android.platform.test.scenario.annotation.Scenario"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- },
- {
- "exclude-annotation": "android.platform.test.annotations.Postsubmit"
- }
- ]
- }
- ],
"presubmit-sysui": [
{
"name": "PlatformScenarioTests",
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 808425435efa..f22e79722e78 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -131,6 +131,9 @@
<!-- For StatusIconContainer to tag its icon views -->
<item type="id" name="status_bar_view_state_tag" />
+ <!-- Status bar -->
+ <item type="id" name="status_bar_dot" />
+
<!-- Default display cutout on the physical top of screen -->
<item type="id" name="display_cutout" />
<item type="id" name="display_cutout_left" />
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
index cdedc64ed0e9..97665145ce76 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -93,8 +93,8 @@ fun View.toBitmap(window: Window? = null): Bitmap {
Futures.addCallback(
captureToBitmap(window),
object : FutureCallback<Bitmap> {
- override fun onSuccess(result: Bitmap) {
- continuation.resumeWith(Result.success(result))
+ override fun onSuccess(result: Bitmap?) {
+ continuation.resumeWith(Result.success(result!!))
}
override fun onFailure(t: Throwable) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index 835d6e92a63d..38a312448bab 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -105,7 +105,8 @@ open class ClockRegistry(
)
}
- pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
+ pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java,
+ true /* allowMultiple */)
context.contentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
false,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 8f5cbb76222f..00236f150488 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -775,6 +775,12 @@ public class AuthContainerView extends LinearLayout
}
mContainerState = STATE_ANIMATING_OUT;
+ // Request hiding soft-keyboard before animating away credential UI, in case IME insets
+ // animation get delayed by dismissing animation.
+ if (isAttachedToWindow() && getRootWindowInsets().isVisible(WindowInsets.Type.ime())) {
+ getWindowInsetsController().hide(WindowInsets.Type.ime());
+ }
+
if (sendReason) {
mPendingCallbackReason = reason;
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 27e9af9b394a..412dc0577876 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -912,6 +912,12 @@ public class UdfpsController implements DozeReceiver {
if (view.isDisplayConfigured()) {
view.unconfigureDisplay();
}
+
+ if (mCancelAodTimeoutAction != null) {
+ mCancelAodTimeoutAction.run();
+ mCancelAodTimeoutAction = null;
+ }
+ mIsAodInterruptActive = false;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 54c1b28bb106..38d9d0210378 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -101,8 +101,8 @@ public class Flags {
public static final UnreleasedFlag MODERN_BOUNCER = new UnreleasedFlag(208);
/** Whether UserSwitcherActivity should use modern architecture. */
- public static final UnreleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
- new UnreleasedFlag(209, true);
+ public static final ReleasedFlag MODERN_USER_SWITCHER_ACTIVITY =
+ new ReleasedFlag(209, true);
/** Whether the new implementation of UserSwitcherController should be used. */
public static final UnreleasedFlag REFACTORED_USER_SWITCHER_CONTROLLER =
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
index f1e54e002ef3..2cd564ff4e32 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt
@@ -149,32 +149,6 @@ class MediaCarouselController @Inject constructor(
}
}
}
-
- companion object {
- private const val SQUISHINESS_SCALE_START = 0.5
- private const val SQUISHINESS_SCALE_FACTOR = 0.5
- private fun getSquishinessScale(squishinessFraction: Float): Double {
- return SQUISHINESS_SCALE_START + SQUISHINESS_SCALE_FACTOR * squishinessFraction
- }
- }
-
- var squishinessFraction: Float = 1f
- set(value) {
- if (field == value) {
- return
- }
- field = value
-
- val scale = getSquishinessScale(field)
- for (mediaPlayer in MediaPlayerData.players()) {
- mediaPlayer.mediaViewHolder?.let {
- it.player.bottom = it.player.top + (scale * it.player.measuredHeight).toInt()
- } ?: mediaPlayer.recommendationViewHolder?.let {
- it.recommendations.bottom = it.recommendations.top +
- (scale * it.recommendations.measuredHeight).toInt()
- }
- }
- }
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onDensityOrFontScaleChanged() {
// System font changes should only happen when UMO is offscreen or a flicker may occur
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index 59c6635ed8bc..2b381a954e27 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -42,7 +42,7 @@ class MediaProjectionAppSelectorController(
}
private fun List<RecentTask>.sortTasks(): List<RecentTask> =
- asReversed().sortedBy {
+ sortedBy {
// Show normal tasks first and only then tasks with opened app selector
it.topActivityComponent == appSelectorComponentName
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
index 0bdddfe2821d..0927f3b00724 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/AppIconLoader.kt
@@ -17,11 +17,17 @@
package com.android.systemui.mediaprojection.appselector.data
import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ComponentInfoFlags
import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import com.android.launcher3.icons.BaseIconFactory.IconOptions
+import com.android.launcher3.icons.IconFactory
import com.android.systemui.dagger.qualifiers.Background
+import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
-import javax.inject.Inject
interface AppIconLoader {
suspend fun loadIcon(userId: Int, component: ComponentName): Drawable?
@@ -31,11 +37,20 @@ class IconLoaderLibAppIconLoader
@Inject
constructor(
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ private val context: Context,
+ private val packageManager: PackageManager
) : AppIconLoader {
override suspend fun loadIcon(userId: Int, component: ComponentName): Drawable? =
withContext(backgroundDispatcher) {
- // TODO(b/240924731): add a blocking call to load an icon using iconloaderlib
- null
+ IconFactory.obtain(context).use<IconFactory, Drawable?> { iconFactory ->
+ val activityInfo = packageManager
+ .getActivityInfo(component, ComponentInfoFlags.of(0))
+ val icon = activityInfo.loadIcon(packageManager) ?: return@withContext null
+ val userHandler = UserHandle.of(userId)
+ val options = IconOptions().apply { setUser(userHandler) }
+ val badgedIcon = iconFactory.createBadgedIconBitmap(icon, options)
+ badgedIcon.newIcon(context)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index 6d67e28e15e4..cd994b857e95 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -16,11 +16,13 @@
package com.android.systemui.mediaprojection.appselector.data
+import android.annotation.ColorInt
import android.content.ComponentName
data class RecentTask(
val taskId: Int,
val userId: Int,
val topActivityComponent: ComponentName?,
- val baseIntentComponent: ComponentName?
+ val baseIntentComponent: ComponentName?,
+ @ColorInt val colorBackground: Int?
)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index 5a0943556d9d..e8b49cd8ec1c 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -16,23 +16,61 @@
package com.android.systemui.mediaprojection.appselector.data
+import android.app.ActivityManager
+import android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.kotlin.getOrNull
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.util.GroupedRecentTaskInfo
+import java.util.Optional
+import javax.inject.Inject
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
-import javax.inject.Inject
+import java.util.concurrent.Executor
interface RecentTaskListProvider {
+ /** Loads recent tasks, the returned task list is from the most-recent to least-recent order */
suspend fun loadRecentTasks(): List<RecentTask>
}
class ShellRecentTaskListProvider
@Inject
-constructor(@Background private val coroutineDispatcher: CoroutineDispatcher) :
- RecentTaskListProvider {
+constructor(
+ @Background private val coroutineDispatcher: CoroutineDispatcher,
+ @Background private val backgroundExecutor: Executor,
+ private val recentTasks: Optional<RecentTasks>
+) : RecentTaskListProvider {
+
+ private val recents by lazy { recentTasks.getOrNull() }
override suspend fun loadRecentTasks(): List<RecentTask> =
withContext(coroutineDispatcher) {
- // TODO(b/240924731): add blocking call to load the recents
- emptyList()
+ val rawRecentTasks: List<GroupedRecentTaskInfo> = recents?.getTasks() ?: emptyList()
+
+ rawRecentTasks
+ .flatMap { listOfNotNull(it.taskInfo1, it.taskInfo2) }
+ .map {
+ RecentTask(
+ it.taskId,
+ it.userId,
+ it.topActivity,
+ it.baseIntent?.component,
+ it.taskDescription?.backgroundColor
+ )
+ }
+ }
+
+ private suspend fun RecentTasks.getTasks(): List<GroupedRecentTaskInfo> =
+ suspendCoroutine { continuation ->
+ getRecentTasks(
+ Integer.MAX_VALUE,
+ RECENT_IGNORE_UNAVAILABLE,
+ ActivityManager.getCurrentUser(),
+ backgroundExecutor
+ ) { tasks ->
+ continuation.resume(tasks)
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
index 429128032f40..47faaed10302 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskThumbnailLoader.kt
@@ -18,6 +18,7 @@ package com.android.systemui.mediaprojection.appselector.data
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.shared.recents.model.ThumbnailData
+import com.android.systemui.shared.system.ActivityManagerWrapper
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
@@ -30,12 +31,13 @@ class ActivityTaskManagerThumbnailLoader
@Inject
constructor(
@Background private val coroutineDispatcher: CoroutineDispatcher,
-) :
- RecentTaskThumbnailLoader {
+ private val activityManager: ActivityManagerWrapper
+) : RecentTaskThumbnailLoader {
override suspend fun loadThumbnail(taskId: Int): ThumbnailData? =
withContext(coroutineDispatcher) {
- // TODO(b/240924731): add blocking call to load a thumbnail
- null
+ val thumbnailData =
+ activityManager.getTaskThumbnail(taskId, /* isLowResolution= */ false)
+ if (thumbnailData.thumbnail == null) null else thumbnailData
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 30947e839f0a..50a10bc0b15a 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -541,10 +541,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
if (!mImeVisible) {
// IME not showing, take all touches
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ return;
}
if (!mView.isImeRenderingNavButtons()) {
// IME showing but not drawing any buttons, take all touches
info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
+ return;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
index f41b905775e4..18bd6b7b3c32 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelController.java
@@ -27,7 +27,6 @@ import android.view.View;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.media.MediaHostState;
@@ -76,14 +75,13 @@ public class QSPanelController extends QSPanelControllerBase<QSPanel> {
@Named(QS_USING_MEDIA_PLAYER) boolean usingMediaPlayer,
@Named(QS_PANEL) MediaHost mediaHost,
QSTileRevealController.Factory qsTileRevealControllerFactory,
- DumpManager dumpManager, MediaCarouselController mediaCarouselController,
- MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
+ DumpManager dumpManager, MetricsLogger metricsLogger, UiEventLogger uiEventLogger,
QSLogger qsLogger, BrightnessController.Factory brightnessControllerFactory,
BrightnessSliderController.Factory brightnessSliderFactory,
FalsingManager falsingManager,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
super(view, qstileHost, qsCustomizerController, usingMediaPlayer, mediaHost,
- metricsLogger, uiEventLogger, qsLogger, dumpManager, mediaCarouselController);
+ metricsLogger, uiEventLogger, qsLogger, dumpManager);
mTunerService = tunerService;
mQsCustomizerController = qsCustomizerController;
mQsTileRevealControllerFactory = qsTileRevealControllerFactory;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
index a5c60a417a05..ded466a0cb25 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanelControllerBase.java
@@ -32,7 +32,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -71,7 +70,6 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
private final UiEventLogger mUiEventLogger;
private final QSLogger mQSLogger;
private final DumpManager mDumpManager;
- private final MediaCarouselController mMediaCarouselController;
protected final ArrayList<TileRecord> mRecords = new ArrayList<>();
protected boolean mShouldUseSplitNotificationShade;
@@ -133,8 +131,7 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
MetricsLogger metricsLogger,
UiEventLogger uiEventLogger,
QSLogger qsLogger,
- DumpManager dumpManager,
- MediaCarouselController mediaCarouselController
+ DumpManager dumpManager
) {
super(view);
mHost = host;
@@ -147,7 +144,6 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
mDumpManager = dumpManager;
mShouldUseSplitNotificationShade =
LargeScreenUtils.shouldUseSplitNotificationShade(getResources());
- mMediaCarouselController = mediaCarouselController;
}
@Override
@@ -165,7 +161,6 @@ public abstract class QSPanelControllerBase<T extends QSPanel> extends ViewContr
public void setSquishinessFraction(float squishinessFraction) {
mView.setSquishinessFraction(squishinessFraction);
- mMediaCarouselController.setSquishinessFraction(squishinessFraction);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index 7ce0ad04bb75..9739974256f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -26,7 +26,6 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.MediaHierarchyManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
@@ -56,10 +55,10 @@ public class QuickQSPanelController extends QSPanelControllerBase<QuickQSPanel>
@Named(QS_USING_COLLAPSED_LANDSCAPE_MEDIA)
Provider<Boolean> usingCollapsedLandscapeMediaProvider,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, MediaCarouselController mediaCarouselController
+ DumpManager dumpManager
) {
super(view, qsTileHost, qsCustomizerController, usingMediaPlayer, mediaHost, metricsLogger,
- uiEventLogger, qsLogger, dumpManager, mediaCarouselController);
+ uiEventLogger, qsLogger, dumpManager);
mUsingCollapsedLandscapeMediaProvider = usingCollapsedLandscapeMediaProvider;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
index b5859616f392..ccaab1adaf26 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeaderController.java
@@ -30,6 +30,7 @@ import com.android.systemui.qs.carrier.QSCarrierGroupController;
import com.android.systemui.qs.dagger.QSScope;
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider;
import com.android.systemui.statusbar.phone.StatusBarIconController;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.StatusIconContainer;
import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.policy.VariableDateViewController;
@@ -104,7 +105,7 @@ class QuickStatusBarHeaderController extends ViewController<QuickStatusBarHeader
mView.requireViewById(R.id.date_clock)
);
- mIconManager = tintedIconManagerFactory.create(mIconContainer);
+ mIconManager = tintedIconManagerFactory.create(mIconContainer, StatusBarLocation.QS);
mDemoModeReceiver = new ClockDemoModeReceiver(mClockView);
mColorExtractor = colorExtractor;
mOnColorsChangedListener = (extractor, which) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
index fe40d4cbe23a..d3ed47407b9d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -48,6 +48,7 @@ import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS
import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
@@ -261,7 +262,7 @@ class LargeScreenShadeHeaderController @Inject constructor(
batteryMeterViewController.ignoreTunerUpdates()
batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
- iconManager = tintedIconManagerFactory.create(iconContainer)
+ iconManager = tintedIconManagerFactory.create(iconContainer, StatusBarLocation.QS)
iconManager.setTint(
Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 827d0d0f8444..c35c5c522798 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -22,6 +22,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
@@ -59,6 +60,8 @@ import com.android.systemui.statusbar.notification.NotificationIconDozeHelper;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.util.drawable.DrawableSize;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -86,6 +89,10 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
public static final int STATE_DOT = 1;
public static final int STATE_HIDDEN = 2;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_ICON, STATE_DOT, STATE_HIDDEN})
+ public @interface VisibleState { }
+
private static final String TAG = "StatusBarIconView";
private static final Property<StatusBarIconView, Float> ICON_APPEAR_AMOUNT
= new FloatProperty<StatusBarIconView>("iconAppearAmount") {
@@ -133,6 +140,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private float mDotRadius;
private int mStaticDotRadius;
+ @StatusBarIconView.VisibleState
private int mVisibleState = STATE_ICON;
private float mIconAppearAmount = 1.0f;
private ObjectAnimator mIconAppearAnimator;
@@ -746,11 +754,12 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
}
@Override
- public void setVisibleState(int state) {
+ public void setVisibleState(@StatusBarIconView.VisibleState int state) {
setVisibleState(state, true /* animate */, null /* endRunnable */);
}
- public void setVisibleState(int state, boolean animate) {
+ @Override
+ public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
setVisibleState(state, animate, null);
}
@@ -862,6 +871,7 @@ public class StatusBarIconView extends AnimatedImageView implements StatusIconDi
return mIconAppearAmount;
}
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 25c6dce96b5c..48c6e273bbb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -59,7 +59,8 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
private ImageView mOut;
private ImageView mMobile, mMobileType, mMobileRoaming;
private View mMobileRoamingSpace;
- private int mVisibleState = -1;
+ @StatusBarIconView.VisibleState
+ private int mVisibleState = STATE_HIDDEN;
private DualToneHandler mDualToneHandler;
private boolean mForceHidden;
@@ -271,7 +272,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
}
@Override
- public void setVisibleState(int state, boolean animate) {
+ public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
if (state == mVisibleState) {
return;
}
@@ -312,6 +313,7 @@ public class StatusBarMobileView extends FrameLayout implements DarkReceiver,
}
@Override
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index 5aee62e3e89f..f3e74d92fc8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -55,7 +55,8 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
private View mAirplaneSpacer;
private WifiIconState mState;
private String mSlot;
- private int mVisibleState = -1;
+ @StatusBarIconView.VisibleState
+ private int mVisibleState = STATE_HIDDEN;
public static StatusBarWifiView fromContext(Context context, String slot) {
LayoutInflater inflater = LayoutInflater.from(context);
@@ -107,7 +108,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
}
@Override
- public void setVisibleState(int state, boolean animate) {
+ public void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate) {
if (state == mVisibleState) {
return;
}
@@ -131,6 +132,7 @@ public class StatusBarWifiView extends BaseStatusBarWifiView implements DarkRece
}
@Override
+ @StatusBarIconView.VisibleState
public int getVisibleState() {
return mVisibleState;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index d541fae4ed33..1196211bd671 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -22,14 +22,32 @@ public interface StatusIconDisplayable extends DarkReceiver {
String getSlot();
void setStaticDrawableColor(int color);
void setDecorColor(int color);
- default void setVisibleState(int state) {
+
+ /** Sets the visible state that this displayable should be. */
+ default void setVisibleState(@StatusBarIconView.VisibleState int state) {
setVisibleState(state, false);
}
- void setVisibleState(int state, boolean animate);
+
+ /**
+ * Sets the visible state that this displayable should be, and whether the change should
+ * animate.
+ */
+ void setVisibleState(@StatusBarIconView.VisibleState int state, boolean animate);
+
+ /** Returns the current visible state of this displayable. */
+ @StatusBarIconView.VisibleState
int getVisibleState();
+
+ /**
+ * Returns true if this icon should be visible if there's space, and false otherwise.
+ *
+ * Note that this doesn't necessarily mean it *will* be visible. It's possible that there are
+ * more icons than space, in which case this icon might just show a dot or might be completely
+ * hidden. {@link #getVisibleState} will return the icon's actual visible status.
+ */
boolean isIconVisible();
+
default boolean isIconBlocked() {
return false;
}
}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index ce2c9c244696..0026b71a5304 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -352,8 +352,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat
mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
mDisableStateTracker.startTracking(mCommandQueue, mView.getDisplay().getDisplayId());
if (mTintedIconManager == null) {
- mTintedIconManager =
- mTintedIconManagerFactory.create(mView.findViewById(R.id.statusIcons));
+ mTintedIconManager = mTintedIconManagerFactory.create(
+ mView.findViewById(R.id.statusIcons), StatusBarLocation.KEYGUARD);
mTintedIconManager.setBlockList(getBlockedIcons());
mStatusBarIconController.addIconGroup(mTintedIconManager);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index bd99713e3a69..d6d021ff2819 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -56,7 +56,6 @@ import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
-import javax.inject.Provider;
public interface StatusBarIconController {
@@ -139,13 +138,15 @@ public interface StatusBarIconController {
public DarkIconManager(
LinearLayout linearLayout,
+ StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(linearLayout,
+ location,
statusBarPipelineFlags,
- wifiViewModelProvider,
+ wifiViewModel,
mobileContextProvider);
mIconHPadding = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_padding);
@@ -204,27 +205,28 @@ public interface StatusBarIconController {
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
private final DarkIconDispatcher mDarkIconDispatcher;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider,
DarkIconDispatcher darkIconDispatcher) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModelProvider = wifiViewModelProvider;
+ mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mDarkIconDispatcher = darkIconDispatcher;
}
- public DarkIconManager create(LinearLayout group) {
+ public DarkIconManager create(LinearLayout group, StatusBarLocation location) {
return new DarkIconManager(
group,
+ location,
mStatusBarPipelineFlags,
- mWifiViewModelProvider,
+ mWifiViewModel,
mMobileContextProvider,
mDarkIconDispatcher);
}
@@ -239,12 +241,14 @@ public interface StatusBarIconController {
public TintedIconManager(
ViewGroup group,
+ StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
super(group,
+ location,
statusBarPipelineFlags,
- wifiViewModelProvider,
+ wifiViewModel,
mobileContextProvider);
}
@@ -278,24 +282,25 @@ public interface StatusBarIconController {
@SysUISingleton
public static class Factory {
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
@Inject
public Factory(
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModelProvider = wifiViewModelProvider;
+ mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
}
- public TintedIconManager create(ViewGroup group) {
+ public TintedIconManager create(ViewGroup group, StatusBarLocation location) {
return new TintedIconManager(
group,
+ location,
mStatusBarPipelineFlags,
- mWifiViewModelProvider,
+ mWifiViewModel,
mMobileContextProvider);
}
}
@@ -306,8 +311,9 @@ public interface StatusBarIconController {
*/
class IconManager implements DemoModeCommandReceiver {
protected final ViewGroup mGroup;
+ private final StatusBarLocation mLocation;
private final StatusBarPipelineFlags mStatusBarPipelineFlags;
- private final Provider<WifiViewModel> mWifiViewModelProvider;
+ private final WifiViewModel mWifiViewModel;
private final MobileContextProvider mMobileContextProvider;
protected final Context mContext;
protected final int mIconSize;
@@ -324,12 +330,14 @@ public interface StatusBarIconController {
public IconManager(
ViewGroup group,
+ StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider mobileContextProvider) {
mGroup = group;
+ mLocation = location;
mStatusBarPipelineFlags = statusBarPipelineFlags;
- mWifiViewModelProvider = wifiViewModelProvider;
+ mWifiViewModel = wifiViewModel;
mMobileContextProvider = mobileContextProvider;
mContext = group.getContext();
mIconSize = mContext.getResources().getDimensionPixelSize(
@@ -446,7 +454,7 @@ public interface StatusBarIconController {
private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
return ModernStatusBarWifiView.constructAndBind(
- mContext, slot, mWifiViewModelProvider.get());
+ mContext, slot, mWifiViewModel, mLocation);
}
private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index a8ad564577dd..76f2dd16bbee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -204,6 +204,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
protected CentralSurfaces mCentralSurfaces;
private NotificationPanelViewController mNotificationPanelViewController;
private BiometricUnlockController mBiometricUnlockController;
+ private boolean mCentralSurfacesRegistered;
private View mNotificationContainer;
@@ -338,6 +339,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mNotificationContainer = notificationContainer;
mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create(
centralSurfaces.getKeyguardMessageArea());
+ mCentralSurfacesRegistered = true;
registerListeners();
}
@@ -1086,6 +1088,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
};
protected void updateStates() {
+ if (!mCentralSurfacesRegistered) {
+ return;
+ }
boolean showing = mShowing;
boolean occluded = mOccluded;
boolean bouncerShowing = bouncerIsShowing();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.kt
new file mode 100644
index 000000000000..5ace22695ec3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocation.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.phone
+
+/** An enumeration of the different locations that host a status bar. */
+enum class StatusBarLocation {
+ /** Home screen or in-app. */
+ HOME,
+ /** Keyguard (aka lockscreen). */
+ KEYGUARD,
+ /** Quick settings (inside the shade). */
+ QS,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 891f657b1c80..b8bdc7dccc31 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -64,6 +64,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
+import com.android.systemui.statusbar.phone.StatusBarLocation;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
@@ -235,7 +236,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mStatusBar.restoreHierarchyState(
savedInstanceState.getSparseParcelableArray(EXTRA_PANEL_STATE));
}
- mDarkIconManager = mDarkIconManagerFactory.create(view.findViewById(R.id.statusIcons));
+ mDarkIconManager = mDarkIconManagerFactory.create(
+ view.findViewById(R.id.statusIcons), StatusBarLocation.HOME);
mDarkIconManager.setShouldLog(true);
updateBlockedIcons();
mStatusBarIconController.addIconGroup(mDarkIconManager);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
new file mode 100644
index 000000000000..118b94c7aa83
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.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.statusbar.pipeline.shared
+
+import android.telephony.TelephonyManager
+import com.android.systemui.Dumpable
+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 are used for calculating connectivity icons.
+ *
+ * Stored in a class for logging purposes.
+ */
+@SysUISingleton
+class ConnectivityConstants
+@Inject
+constructor(dumpManager: DumpManager, telephonyManager: TelephonyManager) : Dumpable {
+ init {
+ dumpManager.registerDumpable("$SB_LOGGING_TAG:ConnectivityConstants", this)
+ }
+
+ /** True if this device has the capability for data connections and false otherwise. */
+ val hasDataCapabilities = telephonyManager.isDataCapable
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.apply { println("hasDataCapabilities=$hasDataCapabilities") }
+ }
+}
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
index 88d8a86d39f2..dbb1aa54d8ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLogger.kt
@@ -33,6 +33,20 @@ class ConnectivityPipelineLogger @Inject constructor(
) {
/**
* Logs a change in one of the **raw inputs** to the connectivity pipeline.
+ *
+ * Use this method for inputs that don't have any extra information besides their callback name.
+ */
+ fun logInputChange(callbackName: String) {
+ buffer.log(
+ SB_LOGGING_TAG,
+ LogLevel.INFO,
+ { str1 = callbackName },
+ { "Input: $str1" }
+ )
+ }
+
+ /**
+ * Logs a change in one of the **raw inputs** to the connectivity pipeline.
*/
fun logInputChange(callbackName: String, changeInfo: String?) {
buffer.log(
@@ -128,12 +142,36 @@ class ConnectivityPipelineLogger @Inject constructor(
const val SB_LOGGING_TAG = "SbConnectivity"
/**
+ * Log a change in one of the **inputs** to the connectivity pipeline.
+ */
+ fun Flow<Unit>.logInputChange(
+ logger: ConnectivityPipelineLogger,
+ inputParamName: String,
+ ): Flow<Unit> {
+ return this.onEach { logger.logInputChange(inputParamName) }
+ }
+
+ /**
+ * Log a change in one of the **inputs** 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> Flow<T>.logInputChange(
+ logger: ConnectivityPipelineLogger,
+ inputParamName: String,
+ prettyPrint: (T) -> String = { it.toString() }
+ ): Flow<T> {
+ return this.onEach {logger.logInputChange(inputParamName, prettyPrint(it)) }
+ }
+
+ /**
* 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(
+ fun <T> Flow<T>.logOutputChange(
logger: ConnectivityPipelineLogger,
outputParamName: String,
prettyPrint: (T) -> String = { it.toString() }
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
index 103f3fc21f91..681cf7254ae7 100644
--- 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
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository
import android.annotation.SuppressLint
+import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
@@ -30,51 +31,87 @@ import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.TrafficStateCallback
import android.util.Log
import com.android.settingslib.Utils
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.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.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import java.util.concurrent.Executor
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
-/**
- * Provides data related to the wifi state.
- */
+/** Provides data related to the wifi state. */
interface WifiRepository {
- /**
- * Observable for the current wifi network.
- */
- val wifiNetwork: Flow<WifiNetworkModel>
-
- /**
- * Observable for the current wifi network activity.
- */
- val wifiActivity: Flow<WifiActivityModel>
+ /** Observable for the current wifi enabled status. */
+ val isWifiEnabled: StateFlow<Boolean>
+
+ /** Observable for the current wifi network. */
+ val wifiNetwork: StateFlow<WifiNetworkModel>
+
+ /** Observable for the current wifi network activity. */
+ val wifiActivity: StateFlow<WifiActivityModel>
}
/** Real implementation of [WifiRepository]. */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
@SuppressLint("MissingPermission")
class WifiRepositoryImpl @Inject constructor(
+ broadcastDispatcher: BroadcastDispatcher,
connectivityManager: ConnectivityManager,
logger: ConnectivityPipelineLogger,
@Main mainExecutor: Executor,
@Application scope: CoroutineScope,
wifiManager: WifiManager?,
) : WifiRepository {
- override val wifiNetwork: Flow<WifiNetworkModel> = conflatedCallbackFlow {
+
+ private val wifiStateChangeEvents: Flow<Unit> = broadcastDispatcher.broadcastFlow(
+ IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION)
+ )
+ .logInputChange(logger, "WIFI_STATE_CHANGED_ACTION intent")
+
+ private val wifiNetworkChangeEvents: MutableSharedFlow<Unit> =
+ MutableSharedFlow(extraBufferCapacity = 1)
+
+ override val isWifiEnabled: StateFlow<Boolean> =
+ if (wifiManager == null) {
+ MutableStateFlow(false).asStateFlow()
+ } else {
+ // Because [WifiManager] doesn't expose a wifi enabled change listener, we do it
+ // internally by fetching [WifiManager.isWifiEnabled] whenever we think the state may
+ // have changed.
+ merge(wifiNetworkChangeEvents, wifiStateChangeEvents)
+ .mapLatest { wifiManager.isWifiEnabled }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "enabled")
+ .stateIn(
+ scope = scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = wifiManager.isWifiEnabled
+ )
+ }
+
+ override val wifiNetwork: StateFlow<WifiNetworkModel> = conflatedCallbackFlow {
var currentWifi: WifiNetworkModel = WIFI_NETWORK_DEFAULT
val callback = object : ConnectivityManager.NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
@@ -84,6 +121,8 @@ class WifiRepositoryImpl @Inject constructor(
) {
logger.logOnCapabilitiesChanged(network, networkCapabilities)
+ wifiNetworkChangeEvents.tryEmit(Unit)
+
val wifiInfo = networkCapabilitiesToWifiInfo(networkCapabilities)
if (wifiInfo?.isPrimary == true) {
val wifiNetworkModel = createWifiNetworkModel(
@@ -104,6 +143,9 @@ class WifiRepositoryImpl @Inject constructor(
override fun onLost(network: Network) {
logger.logOnLost(network)
+
+ wifiNetworkChangeEvents.tryEmit(Unit)
+
val wifi = currentWifi
if (wifi is WifiNetworkModel.Active && wifi.networkId == network.getNetId()) {
val newNetworkModel = WifiNetworkModel.Inactive
@@ -132,7 +174,7 @@ class WifiRepositoryImpl @Inject constructor(
initialValue = WIFI_NETWORK_DEFAULT
)
- override val wifiActivity: Flow<WifiActivityModel> =
+ override val wifiActivity: StateFlow<WifiActivityModel> =
if (wifiManager == null) {
Log.w(SB_LOGGING_TAG, "Null WifiManager; skipping activity callback")
flowOf(ACTIVITY_DEFAULT)
@@ -142,13 +184,15 @@ class WifiRepositoryImpl @Inject constructor(
logger.logInputChange("onTrafficStateChange", prettyPrintActivity(state))
trySend(trafficStateToWifiActivityModel(state))
}
-
- trySend(ACTIVITY_DEFAULT)
wifiManager.registerTrafficStateCallback(mainExecutor, callback)
-
awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
}
}
+ .stateIn(
+ scope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = ACTIVITY_DEFAULT
+ )
companion object {
val ACTIVITY_DEFAULT = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
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
index 952525d243f9..04b17ed2924a 100644
--- 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
@@ -22,9 +22,10 @@ import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlo
import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
/**
@@ -38,7 +39,11 @@ class WifiInteractor @Inject constructor(
connectivityRepository: ConnectivityRepository,
wifiRepository: WifiRepository,
) {
- private val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
+ /**
+ * The SSID (service set identifier) of the wifi network. Null if we don't have a network, or
+ * have a network but no valid SSID.
+ */
+ val ssid: Flow<String?> = wifiRepository.wifiNetwork.map { info ->
when (info) {
is WifiNetworkModel.Inactive -> null
is WifiNetworkModel.CarrierMerged -> null
@@ -51,17 +56,17 @@ class WifiInteractor @Inject constructor(
}
}
+ /** Our current enabled status. */
+ val isEnabled: Flow<Boolean> = wifiRepository.isWifiEnabled
+
/** Our current wifi network. See [WifiNetworkModel]. */
val wifiNetwork: Flow<WifiNetworkModel> = wifiRepository.wifiNetwork
+ /** Our current wifi activity. See [WifiActivityModel]. */
+ val activity: StateFlow<WifiActivityModel> = wifiRepository.wifiActivity
+
/** True if we're configured to force-hide the wifi icon and false otherwise. */
val isForceHidden: Flow<Boolean> = connectivityRepository.forceHiddenSlots.map {
it.contains(ConnectivitySlot.WIFI)
}
-
- /** True if our wifi network has activity in (download), and false otherwise. */
- val hasActivityIn: Flow<Boolean> =
- combine(wifiRepository.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
index a19d1bdd8e62..0eb4b0de9f6b 100644
--- 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
@@ -41,9 +41,14 @@ class WifiConstants @Inject constructor(
/** True if we should show the activityIn/activityOut icons and false otherwise. */
val shouldShowActivityConfig = context.resources.getBoolean(R.bool.config_showActivity)
+ /** True if we should always show the wifi icon when wifi is enabled and false otherwise. */
+ val alwaysShowIconIfEnabled =
+ context.resources.getBoolean(R.bool.config_showWifiIndicatorWhenEnabled)
+
override fun dump(pw: PrintWriter, args: Array<out String>) {
pw.apply {
println("shouldShowActivityConfig=$shouldShowActivityConfig")
+ println("alwaysShowIconIfEnabled=$alwaysShowIconIfEnabled")
}
}
}
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/shared/model/WifiActivityModel.kt
index 44c04968041e..574610605b4e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/model/WifiActivityModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiActivityModel.kt
@@ -14,11 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.pipeline.wifi.data.model
+package com.android.systemui.statusbar.pipeline.wifi.shared.model
-/**
- * Provides information on the current wifi activity.
- */
+/** Provides information on the current wifi activity. */
data class WifiActivityModel(
/** True if the wifi has activity in (download). */
val hasActivityIn: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 4fad3274d12f..273be63eb8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -26,8 +26,15 @@ import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.R
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import kotlinx.coroutines.InternalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
@@ -41,40 +48,111 @@ import kotlinx.coroutines.launch
*/
@OptIn(InternalCoroutinesApi::class)
object WifiViewBinder {
- /** Binds the view to the view-model, continuing to update the former based on the latter. */
+
+ /**
+ * Defines interface for an object that acts as the binding between the view and its view-model.
+ *
+ * Users of the [WifiViewBinder] class should use this to control the binder after it is bound.
+ */
+ interface Binding {
+ /** Returns true if the wifi icon should be visible and false otherwise. */
+ fun getShouldIconBeVisible(): Boolean
+
+ /** Notifies that the visibility state has changed. */
+ fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
+ }
+
+ /**
+ * Binds the view to the appropriate view-model based on the given location. The view will
+ * continue to be updated following updates from the view-model.
+ */
@JvmStatic
fun bind(
view: ViewGroup,
- viewModel: WifiViewModel,
- ) {
+ wifiViewModel: WifiViewModel,
+ location: StatusBarLocation,
+ ): Binding {
+ return when (location) {
+ StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
+ StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
+ StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
+ }
+ }
+
+ /** Binds the view to the view-model, continuing to update the former based on the latter. */
+ @JvmStatic
+ private fun bind(
+ view: ViewGroup,
+ viewModel: LocationBasedWifiViewModel,
+ ): Binding {
+ val groupView = view.requireViewById<ViewGroup>(R.id.wifi_group)
val iconView = view.requireViewById<ImageView>(R.id.wifi_signal)
+ val dotView = view.requireViewById<StatusBarIconView>(R.id.status_bar_dot)
+ val activityInView = view.requireViewById<ImageView>(R.id.wifi_in)
+ val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out)
+ val activityContainerView = view.requireViewById<View>(R.id.inout_container)
view.isVisible = true
iconView.isVisible = true
+ // TODO(b/238425913): We should log this visibility state.
+ @StatusBarIconView.VisibleState
+ val visibilityState: MutableStateFlow<Int> = MutableStateFlow(STATE_HIDDEN)
+
view.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
- viewModel.wifiIcon.distinctUntilChanged().collect { wifiIcon ->
- // TODO(b/238425913): Right now, if !isVisible, there's just an empty space
- // where the wifi icon would be. We need to pipe isVisible through to
- // [ModernStatusBarWifiView.isIconVisible], which is what actually makes
- // the view GONE.
+ visibilityState.collect { visibilityState ->
+ groupView.isVisible = visibilityState == STATE_ICON
+ dotView.isVisible = visibilityState == STATE_DOT
+ }
+ }
+
+ launch {
+ viewModel.wifiIcon.collect { wifiIcon ->
view.isVisible = wifiIcon != null
- wifiIcon?.let {
- IconViewBinder.bind(wifiIcon, iconView)
- }
+ wifiIcon?.let { IconViewBinder.bind(wifiIcon, iconView) }
}
}
launch {
viewModel.tint.collect { tint ->
- iconView.imageTintList = ColorStateList.valueOf(tint)
+ val tintList = ColorStateList.valueOf(tint)
+ iconView.imageTintList = tintList
+ activityInView.imageTintList = tintList
+ activityOutView.imageTintList = tintList
+ dotView.setDecorColor(tint)
+ }
+ }
+
+ launch {
+ viewModel.isActivityInViewVisible.distinctUntilChanged().collect { visible ->
+ activityInView.isVisible = visible
+ }
+ }
+
+ launch {
+ viewModel.isActivityOutViewVisible.distinctUntilChanged().collect { visible ->
+ activityOutView.isVisible = visible
+ }
+ }
+
+ launch {
+ viewModel.isActivityContainerVisible.distinctUntilChanged().collect { visible ->
+ activityContainerView.isVisible = visible
}
}
}
}
- // TODO(b/238425913): Hook up to [viewModel] to render actual changes to the wifi icon.
+ return object : Binding {
+ override fun getShouldIconBeVisible(): Boolean {
+ return viewModel.wifiIcon.value != null
+ }
+
+ override fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int) {
+ visibilityState.value = state
+ }
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index c14a897fffab..6c616ac7c3b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -19,10 +19,14 @@ package com.android.systemui.statusbar.pipeline.wifi.ui.view
import android.content.Context
import android.graphics.Rect
import android.util.AttributeSet
+import android.view.Gravity
import android.view.LayoutInflater
import com.android.systemui.R
import com.android.systemui.statusbar.BaseStatusBarWifiView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
@@ -36,6 +40,17 @@ class ModernStatusBarWifiView(
) : BaseStatusBarWifiView(context, attrs) {
private lateinit var slot: String
+ private lateinit var binding: WifiViewBinder.Binding
+
+ @StatusBarIconView.VisibleState
+ private var iconVisibleState: Int = STATE_HIDDEN
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ binding.onVisibilityStateChanged(value)
+ }
override fun onDarkChanged(areas: ArrayList<Rect>?, darkIntensity: Float, tint: Int) {
// TODO(b/238425913)
@@ -51,42 +66,64 @@ class ModernStatusBarWifiView(
// TODO(b/238425913)
}
- override fun setVisibleState(state: Int, animate: Boolean) {
- // TODO(b/238425913)
+ override fun setVisibleState(@StatusBarIconView.VisibleState state: Int, animate: Boolean) {
+ iconVisibleState = state
}
+ @StatusBarIconView.VisibleState
override fun getVisibleState(): Int {
- // TODO(b/238425913)
- return STATE_ICON
+ return iconVisibleState
}
override fun isIconVisible(): Boolean {
- // TODO(b/238425913)
- return true
+ return binding.getShouldIconBeVisible()
}
- /** Set the slot name for this view. */
- private fun setSlot(slotName: String) {
- this.slot = slotName
+ private fun initView(
+ slotName: String,
+ wifiViewModel: WifiViewModel,
+ location: StatusBarLocation,
+ ) {
+ slot = slotName
+ initDotView()
+ binding = WifiViewBinder.bind(this, wifiViewModel, location)
+ }
+
+ // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
+ private fun initDotView() {
+ // TODO(b/238425913): Could we just have this dot view be part of
+ // R.layout.new_status_bar_wifi_group with a dot drawable so we don't need to inflate it
+ // manually? Would that not work with animations?
+ val dotView = StatusBarIconView(mContext, slot, null).also {
+ it.id = R.id.status_bar_dot
+ // Hard-code this view to always be in the DOT state so that whenever it's visible it
+ // will show a dot
+ it.visibleState = STATE_DOT
+ }
+
+ val width = mContext.resources.getDimensionPixelSize(R.dimen.status_bar_icon_size)
+ val lp = LayoutParams(width, width)
+ lp.gravity = Gravity.CENTER_VERTICAL or Gravity.START
+ addView(dotView, lp)
}
companion object {
/**
- * Inflates a new instance of [ModernStatusBarWifiView], binds it to [viewModel], and
+ * Inflates a new instance of [ModernStatusBarWifiView], binds it to a view model, and
* returns it.
*/
@JvmStatic
fun constructAndBind(
context: Context,
slot: String,
- viewModel: WifiViewModel,
+ wifiViewModel: WifiViewModel,
+ location: StatusBarLocation,
): ModernStatusBarWifiView {
return (
LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
as ModernStatusBarWifiView
).also {
- it.setSlot(slot)
- WifiViewBinder.bind(it, viewModel)
+ it.initView(slot, wifiViewModel, location)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
new file mode 100644
index 000000000000..871b395d0996
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/HomeWifiViewModel.kt
@@ -0,0 +1,43 @@
+/*
+ * 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 android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/**
+ * A view model for the wifi icon shown on the "home" page (aka, when the device is unlocked and not
+ * showing the shade, so the user is on the home-screen, or in an app).
+ */
+class HomeWifiViewModel(
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ wifiIcon: StateFlow<Icon?>,
+ isActivityInViewVisible: Flow<Boolean>,
+ isActivityOutViewVisible: Flow<Boolean>,
+ isActivityContainerVisible: Flow<Boolean>,
+) :
+ LocationBasedWifiViewModel(
+ statusBarPipelineFlags,
+ debugTint = Color.CYAN,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.kt
new file mode 100644
index 000000000000..be1f3f2194bc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/KeyguardWifiViewModel.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.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** A view model for the wifi icon shown on keyguard (lockscreen). */
+class KeyguardWifiViewModel(
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ wifiIcon: StateFlow<Icon?>,
+ isActivityInViewVisible: Flow<Boolean>,
+ isActivityOutViewVisible: Flow<Boolean>,
+ isActivityContainerVisible: Flow<Boolean>,
+) :
+ LocationBasedWifiViewModel(
+ statusBarPipelineFlags,
+ debugTint = Color.MAGENTA,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
new file mode 100644
index 000000000000..7243acfbd56d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/LocationBasedWifiViewModel.kt
@@ -0,0 +1,68 @@
+/*
+ * 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 android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+
+/**
+ * A view model for a wifi icon in a specific location. This allows us to control parameters that
+ * are location-specific (for example, different tints of the icon in different locations).
+ *
+ * Must be subclassed for each distinct location.
+ */
+abstract class LocationBasedWifiViewModel(
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ debugTint: Int,
+
+ /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
+ val wifiIcon: StateFlow<Icon?>,
+
+ /** True if the activity in view should be visible. */
+ val isActivityInViewVisible: Flow<Boolean>,
+
+ /** True if the activity out view should be visible. */
+ val isActivityOutViewVisible: Flow<Boolean>,
+
+ /** True if the activity container view should be visible. */
+ val isActivityContainerVisible: Flow<Boolean>,
+) {
+ /** The color that should be used to tint the icon. */
+ val tint: Flow<Int> =
+ flowOf(
+ if (statusBarPipelineFlags.useNewPipelineDebugColoring()) {
+ debugTint
+ } else {
+ DEFAULT_TINT
+ }
+ )
+
+ companion object {
+ /**
+ * A default icon tint.
+ *
+ * TODO(b/238425913): The tint is actually controlled by
+ * [com.android.systemui.statusbar.phone.StatusBarIconController.TintedIconManager]. We
+ * should use that logic instead of white as a default.
+ */
+ private const val DEFAULT_TINT = Color.WHITE
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.kt
new file mode 100644
index 000000000000..d640d33eb316
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/QsWifiViewModel.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.ui.viewmodel
+
+import android.graphics.Color
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
+
+/** A view model for the wifi icon shown in quick settings (when the shade is pulled down). */
+class QsWifiViewModel(
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ wifiIcon: StateFlow<Icon?>,
+ isActivityInViewVisible: Flow<Boolean>,
+ isActivityOutViewVisible: Flow<Boolean>,
+ isActivityContainerVisible: Flow<Boolean>,
+) :
+ LocationBasedWifiViewModel(
+ statusBarPipelineFlags,
+ debugTint = Color.GREEN,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
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
index 3c243ac90831..295bdfb758d8 100644
--- 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
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel
import android.content.Context
-import android.graphics.Color
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
@@ -26,107 +25,185 @@ import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTI
import com.android.systemui.R
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
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.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
/**
* Models the UI state for the status bar wifi icon.
+ *
+ * This class exposes three view models, one per status bar location:
+ * - [home]
+ * - [keyguard]
+ * - [qs]
+ * In order to get the UI state for the wifi icon, you must use one of those view models (whichever
+ * is correct for your location).
+ *
+ * Internally, this class maintains the current state of the wifi icon and notifies those three
+ * view models of any changes.
*/
-class WifiViewModel @Inject constructor(
- statusBarPipelineFlags: StatusBarPipelineFlags,
- private val constants: WifiConstants,
+@SysUISingleton
+class WifiViewModel
+@Inject
+constructor(
+ connectivityConstants: ConnectivityConstants,
private val context: Context,
- private val logger: ConnectivityPipelineLogger,
- private val interactor: WifiInteractor,
+ logger: ConnectivityPipelineLogger,
+ interactor: WifiInteractor,
+ @Application private val scope: CoroutineScope,
+ statusBarPipelineFlags: StatusBarPipelineFlags,
+ wifiConstants: WifiConstants,
) {
/**
- * The drawable resource ID to use for the wifi icon. Null if we shouldn't display any icon.
+ * Returns the drawable resource ID to use for the wifi icon based on the given network.
+ * Null if we can't compute the icon.
*/
@DrawableRes
- private val iconResId: Flow<Int?> = interactor.wifiNetwork.map {
- when (it) {
+ private fun WifiNetworkModel.iconResId(): Int? {
+ return when (this) {
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Inactive -> WIFI_NO_NETWORK
is WifiNetworkModel.Active ->
when {
- it.level == null -> null
- it.isValidated -> WIFI_FULL_ICONS[it.level]
- else -> WIFI_NO_INTERNET_ICONS[it.level]
+ this.level == null -> null
+ this.isValidated -> WIFI_FULL_ICONS[this.level]
+ else -> WIFI_NO_INTERNET_ICONS[this.level]
}
}
}
- /** The content description for the wifi icon. */
- private val contentDescription: Flow<ContentDescription?> = interactor.wifiNetwork.map {
- when (it) {
+ /**
+ * Returns the content description for the wifi icon based on the given network.
+ * Null if we can't compute the content description.
+ */
+ private fun WifiNetworkModel.contentDescription(): ContentDescription? {
+ return when (this) {
is WifiNetworkModel.CarrierMerged -> null
is WifiNetworkModel.Inactive ->
ContentDescription.Loaded(
"${context.getString(WIFI_NO_CONNECTION)},${context.getString(NO_INTERNET)}"
)
is WifiNetworkModel.Active ->
- when (it.level) {
+ when (this.level) {
null -> null
else -> {
- val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[it.level])
+ val levelDesc = context.getString(WIFI_CONNECTION_STRENGTH[this.level])
when {
- it.isValidated -> ContentDescription.Loaded(levelDesc)
- else -> ContentDescription.Loaded(
- "$levelDesc,${context.getString(NO_INTERNET)}"
- )
+ this.isValidated -> ContentDescription.Loaded(levelDesc)
+ else ->
+ ContentDescription.Loaded(
+ "$levelDesc,${context.getString(NO_INTERNET)}"
+ )
}
}
}
}
}
- /**
- * The wifi icon that should be displayed. Null if we shouldn't display any icon.
- */
- val wifiIcon: Flow<Icon?> = combine(
+ /** The wifi icon that should be displayed. Null if we shouldn't display any icon. */
+ private val wifiIcon: StateFlow<Icon?> =
+ combine(
+ interactor.isEnabled,
interactor.isForceHidden,
- iconResId,
- contentDescription,
- ) { isForceHidden, iconResId, contentDescription ->
- when {
- isForceHidden ||
- iconResId == null ||
- iconResId <= 0 -> null
- else -> Icon.Resource(iconResId, contentDescription)
+ interactor.wifiNetwork,
+ ) { isEnabled, isForceHidden, wifiNetwork ->
+ if (!isEnabled || isForceHidden || wifiNetwork is WifiNetworkModel.CarrierMerged) {
+ return@combine null
+ }
+
+ val iconResId = wifiNetwork.iconResId() ?: return@combine null
+ val icon = Icon.Resource(iconResId, wifiNetwork.contentDescription())
+
+ return@combine when {
+ wifiConstants.alwaysShowIconIfEnabled -> icon
+ !connectivityConstants.hasDataCapabilities -> icon
+ wifiNetwork is WifiNetworkModel.Active && wifiNetwork.isValidated -> icon
+ else -> null
}
}
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
- /**
- * True if the activity in icon should be displayed and false otherwise.
- */
- val isActivityInVisible: Flow<Boolean>
- get() =
- if (!constants.shouldShowActivityConfig) {
- flowOf(false)
- } else {
- interactor.hasActivityIn
+ /** The wifi activity status. Null if we shouldn't display the activity status. */
+ private val activity: Flow<WifiActivityModel?> =
+ if (!wifiConstants.shouldShowActivityConfig) {
+ flowOf(null)
+ } else {
+ combine(interactor.activity, interactor.ssid) { activity, ssid ->
+ when (ssid) {
+ null -> null
+ else -> activity
+ }
}
- .logOutputChange(logger, "activityInVisible")
+ }
+ .distinctUntilChanged()
+ .logOutputChange(logger, "activity")
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
- /** The tint that should be applied to the icon. */
- val tint: Flow<Int> = if (!statusBarPipelineFlags.useNewPipelineDebugColoring()) {
- emptyFlow()
- } else {
- flowOf(Color.CYAN)
- }
+ private val isActivityInViewVisible: Flow<Boolean> =
+ activity
+ .map { it?.hasActivityIn == true }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ private val isActivityOutViewVisible: Flow<Boolean> =
+ activity
+ .map { it?.hasActivityOut == true }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ private val isActivityContainerVisible: Flow<Boolean> =
+ combine(isActivityInViewVisible, isActivityOutViewVisible) { activityIn, activityOut ->
+ activityIn || activityOut
+ }
+ .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = false)
+
+ /** A view model for the status bar on the home screen. */
+ val home: HomeWifiViewModel =
+ HomeWifiViewModel(
+ statusBarPipelineFlags,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
+
+ /** A view model for the status bar on keyguard. */
+ val keyguard: KeyguardWifiViewModel =
+ KeyguardWifiViewModel(
+ statusBarPipelineFlags,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
+
+ /** A view model for the status bar in quick settings. */
+ val qs: QsWifiViewModel =
+ QsWifiViewModel(
+ statusBarPipelineFlags,
+ wifiIcon,
+ isActivityInViewVisible,
+ isActivityOutViewVisible,
+ isActivityContainerVisible,
+ )
companion object {
@StringRes
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index a6c0539a8281..53e30fdb0a4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -18,6 +18,7 @@ package com.android.systemui.biometrics;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
import static junit.framework.Assert.assertEquals;
@@ -687,6 +688,58 @@ public class UdfpsControllerTest extends SysuiTestCase {
}
@Test
+ public void aodInterruptCancelTimeoutActionWhenFingerUp() throws RemoteException {
+ when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
+ // GIVEN AOD interrupt
+ mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+ BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+ mScreenObserver.onScreenTurnedOn();
+ mFgExecutor.runAllReady();
+ mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
+ mFgExecutor.runAllReady();
+
+ // Configure UdfpsView to accept the ACTION_UP event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+ // WHEN ACTION_UP is received
+ verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+ MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent);
+ mBiometricsExecutor.runAllReady();
+ upEvent.recycle();
+
+ // Configure UdfpsView to accept the ACTION_DOWN event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+
+ // WHEN ACTION_DOWN is received
+ MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
+ mBiometricsExecutor.runAllReady();
+ downEvent.recycle();
+
+ // WHEN ACTION_MOVE is received
+ MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
+ mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
+ mBiometricsExecutor.runAllReady();
+ moveEvent.recycle();
+ mFgExecutor.runAllReady();
+
+ // Configure UdfpsView to accept the finger up event
+ when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+
+ // WHEN it times out
+ mFgExecutor.advanceClockToNext();
+ mFgExecutor.runAllReady();
+
+ // THEN the display should be unconfigured once. If the timeout action is not
+ // cancelled, the display would be unconfigured twice which would cause two
+ // FP attempts.
+ verify(mUdfpsView, times(1)).unconfigureDisplay();
+ }
+
+ @Test
public void aodInterruptScreenOff() throws RemoteException {
// GIVEN screen off
mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index b42b7695cedd..8b1554c1f66f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -48,6 +48,7 @@ import android.view.WindowManagerPolicyConstants;
import android.window.OnBackInvokedCallback;
import android.window.OnBackInvokedDispatcher;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -234,6 +235,11 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
verify(mOnBackInvokedDispatcher).unregisterOnBackInvokedCallback(any());
}
+ /**
+ * This specific test case appears to be flaky.
+ * b/249136797 tracks the task of root-causing and fixing it.
+ */
+ @FlakyTest
@Test
public void testPredictiveBackInvocationDismissesDialog() {
mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
index e3e3b7413157..5ad354247a04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt
@@ -30,7 +30,6 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.statusbar.notification.collection.provider.OnReorderingAllowedListener
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider
import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
@@ -46,7 +45,6 @@ import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
-import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@@ -73,10 +71,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
@Mock lateinit var dumpManager: DumpManager
@Mock lateinit var logger: MediaUiEventLogger
@Mock lateinit var debugLogger: MediaCarouselControllerLogger
- @Mock lateinit var mediaViewHolder: MediaViewHolder
- @Mock lateinit var player: TransitionLayout
- @Mock lateinit var recommendationViewHolder: RecommendationViewHolder
- @Mock lateinit var recommendations: TransitionLayout
@Mock lateinit var mediaPlayer: MediaControlPanel
@Mock lateinit var mediaViewController: MediaViewController
@Mock lateinit var smartspaceMediaData: SmartspaceMediaData
@@ -282,46 +276,6 @@ class MediaCarouselControllerTest : SysuiTestCase() {
verify(logger).logRecommendationRemoved(eq(packageName), eq(instanceId!!))
}
- @Test
- fun testSetSquishinessFractionForMedia_setPlayerBottom() {
- whenever(panel.mediaViewHolder).thenReturn(mediaViewHolder)
- whenever(mediaViewHolder.player).thenReturn(player)
- whenever(player.measuredHeight).thenReturn(100)
-
- val playingLocal = Triple("playing local",
- DATA.copy(active = true, isPlaying = true,
- playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = false),
- 4500L)
- MediaPlayerData.addMediaPlayer(playingLocal.first, playingLocal.second, panel, clock,
- false, debugLogger)
-
- mediaCarouselController.squishinessFraction = 0.0f
- verify(player).bottom = 50
- verifyNoMoreInteractions(recommendationViewHolder)
-
- mediaCarouselController.squishinessFraction = 0.5f
- verify(player).bottom = 75
- verifyNoMoreInteractions(recommendationViewHolder)
- }
-
- @Test
- fun testSetSquishinessFractionForRecommendation_setPlayerBottom() {
- whenever(panel.recommendationViewHolder).thenReturn(recommendationViewHolder)
- whenever(recommendationViewHolder.recommendations).thenReturn(recommendations)
- whenever(recommendations.measuredHeight).thenReturn(100)
-
- MediaPlayerData.addMediaRecommendation(SMARTSPACE_KEY, EMPTY_SMARTSPACE_MEDIA_DATA, panel,
- false, clock)
-
- mediaCarouselController.squishinessFraction = 0.0f
- verifyNoMoreInteractions(mediaViewHolder)
- verify(recommendationViewHolder.recommendations).bottom = 50
-
- mediaCarouselController.squishinessFraction = 0.5f
- verifyNoMoreInteractions(mediaViewHolder)
- verify(recommendationViewHolder.recommendations).bottom = 75
- }
-
fun testMediaLoaded_ScrollToActivePlayer() {
listener.value.onMediaDataLoaded("playing local",
null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 37b7f47fb69e..00b1f3268bae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -54,7 +54,7 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
}
@Test
- fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInReverse() {
+ fun initMultipleRecentTasksWithoutAppSelectorTask_bindsListInTheSameOrder() {
val tasks = listOf(
createRecentTask(taskId = 1),
createRecentTask(taskId = 2),
@@ -66,15 +66,15 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
verify(view).bind(
listOf(
- createRecentTask(taskId = 3),
- createRecentTask(taskId = 2),
createRecentTask(taskId = 1),
+ createRecentTask(taskId = 2),
+ createRecentTask(taskId = 3),
)
)
}
@Test
- fun initRecentTasksWithAppSelectorTasks_bindsListInReverseAndAppSelectorTasksAtTheEnd() {
+ fun initRecentTasksWithAppSelectorTasks_bindsAppSelectorTasksAtTheEnd() {
val tasks = listOf(
createRecentTask(taskId = 1),
createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
@@ -88,11 +88,11 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
verify(view).bind(
listOf(
- createRecentTask(taskId = 5),
- createRecentTask(taskId = 3),
createRecentTask(taskId = 1),
- createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 5),
createRecentTask(taskId = 2, topActivityComponent = appSelectorComponentName),
+ createRecentTask(taskId = 4, topActivityComponent = appSelectorComponentName),
)
)
}
@@ -105,7 +105,8 @@ class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
taskId = taskId,
topActivityComponent = topActivityComponent,
baseIntentComponent = ComponentName("com", "Test"),
- userId = 0
+ userId = 0,
+ colorBackground = 0
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
new file mode 100644
index 000000000000..939af16d81cd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -0,0 +1,112 @@
+package com.android.systemui.mediaprojection.appselector.data
+
+import android.app.ActivityManager.RecentTaskInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.util.GroupedRecentTaskInfo
+import com.google.common.truth.Truth.assertThat
+import java.util.*
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.function.Consumer
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ShellRecentTaskListProviderTest : SysuiTestCase() {
+
+ private val dispatcher = Dispatchers.Unconfined
+ private val recentTasks: RecentTasks = mock()
+ private val recentTaskListProvider =
+ ShellRecentTaskListProvider(dispatcher, Runnable::run, Optional.of(recentTasks))
+
+ @Test
+ fun loadRecentTasks_oneTask_returnsTheSameTask() {
+ givenRecentTasks(createSingleTask(taskId = 1))
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result).containsExactly(createRecentTask(taskId = 1))
+ }
+
+ @Test
+ fun loadRecentTasks_multipleTasks_returnsTheSameTasks() {
+ givenRecentTasks(
+ createSingleTask(taskId = 1),
+ createSingleTask(taskId = 2),
+ createSingleTask(taskId = 3),
+ )
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result)
+ .containsExactly(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 2),
+ createRecentTask(taskId = 3),
+ )
+ }
+
+ @Test
+ fun loadRecentTasks_groupedTask_returnsUngroupedTasks() {
+ givenRecentTasks(createTaskPair(taskId1 = 1, taskId2 = 2))
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result)
+ .containsExactly(createRecentTask(taskId = 1), createRecentTask(taskId = 2))
+ }
+
+ @Test
+ fun loadRecentTasks_mixedSingleAndGroupedTask_returnsUngroupedTasks() {
+ givenRecentTasks(
+ createSingleTask(taskId = 1),
+ createTaskPair(taskId1 = 2, taskId2 = 3),
+ createSingleTask(taskId = 4),
+ createTaskPair(taskId1 = 5, taskId2 = 6),
+ )
+
+ val result = runBlocking { recentTaskListProvider.loadRecentTasks() }
+
+ assertThat(result)
+ .containsExactly(
+ createRecentTask(taskId = 1),
+ createRecentTask(taskId = 2),
+ createRecentTask(taskId = 3),
+ createRecentTask(taskId = 4),
+ createRecentTask(taskId = 5),
+ createRecentTask(taskId = 6),
+ )
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun givenRecentTasks(vararg tasks: GroupedRecentTaskInfo) {
+ whenever(recentTasks.getRecentTasks(any(), any(), any(), any(), any())).thenAnswer {
+ val consumer = it.arguments.last() as Consumer<List<GroupedRecentTaskInfo>>
+ consumer.accept(tasks.toList())
+ }
+ }
+
+ private fun createRecentTask(taskId: Int): RecentTask =
+ RecentTask(
+ taskId = taskId,
+ userId = 0,
+ topActivityComponent = null,
+ baseIntentComponent = null,
+ colorBackground = null
+ )
+
+ private fun createSingleTask(taskId: Int): GroupedRecentTaskInfo =
+ GroupedRecentTaskInfo.forSingleTask(createTaskInfo(taskId))
+
+ private fun createTaskPair(taskId1: Int, taskId2: Int): GroupedRecentTaskInfo =
+ GroupedRecentTaskInfo.forSplitTasks(createTaskInfo(taskId1), createTaskInfo(taskId2), null)
+
+ private fun createTaskInfo(taskId: Int) = RecentTaskInfo().apply { this.taskId = taskId }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
index cbe118635e95..3cad2a005882 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerBaseTest.java
@@ -44,7 +44,6 @@ import com.android.internal.logging.testing.UiEventLoggerFake;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.media.MediaCarouselController;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.qs.QSTileView;
@@ -87,7 +86,6 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
@Mock
private QSLogger mQSLogger;
private DumpManager mDumpManager = new DumpManager();
- private MediaCarouselController mMediaCarouselController;
@Mock
QSTileImpl mQSTile;
@Mock
@@ -110,9 +108,9 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
protected TestableQSPanelControllerBase(QSPanel view, QSTileHost host,
QSCustomizerController qsCustomizerController, MediaHost mediaHost,
MetricsLogger metricsLogger, UiEventLogger uiEventLogger, QSLogger qsLogger,
- DumpManager dumpManager, MediaCarouselController mediaCarouselController) {
+ DumpManager dumpManager) {
super(view, host, qsCustomizerController, true, mediaHost, metricsLogger, uiEventLogger,
- qsLogger, dumpManager, mediaCarouselController);
+ qsLogger, dumpManager);
}
@Override
@@ -146,7 +144,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
reset(mQSTileRevealController);
@@ -158,7 +156,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
QSPanelControllerBase<QSPanel> controller = new TestableQSPanelControllerBase(mQSPanel,
mQSTileHost, mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController) {
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager) {
@Override
protected QSTileRevealController createTileRevealController() {
return mQSTileRevealController;
@@ -253,7 +251,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
when(mQSPanel.getDumpableTag()).thenReturn("QSPanelLandscape");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isTrue();
@@ -262,7 +260,7 @@ public class QSPanelControllerBaseTest extends SysuiTestCase {
when(mQSPanel.getDumpableTag()).thenReturn("QSPanelPortrait");
mController = new TestableQSPanelControllerBase(mQSPanel, mQSTileHost,
mQSCustomizerController, mMediaHost,
- mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager, mMediaCarouselController);
+ mMetricsLogger, mUiEventLogger, mQSLogger, mDumpManager);
mController.init();
assertThat(mController.shouldUseHorizontalLayout()).isFalse();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
index 98d499a70fa7..5eb9a9862340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt
@@ -6,7 +6,6 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaCarouselController
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.FalsingManager
@@ -41,7 +40,6 @@ class QSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var qsCustomizerController: QSCustomizerController
@Mock private lateinit var qsTileRevealControllerFactory: QSTileRevealController.Factory
@Mock private lateinit var dumpManager: DumpManager
- @Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var metricsLogger: MetricsLogger
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var qsLogger: QSLogger
@@ -78,7 +76,6 @@ class QSPanelControllerTest : SysuiTestCase() {
mediaHost,
qsTileRevealControllerFactory,
dumpManager,
- mediaCarouselController,
metricsLogger,
uiEventLogger,
qsLogger,
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 4af5b9018d5a..6af8e4904a1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt
@@ -23,7 +23,6 @@ import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
-import com.android.systemui.media.MediaCarouselController
import com.android.systemui.media.MediaHost
import com.android.systemui.media.MediaHostState
import com.android.systemui.plugins.qs.QSTile
@@ -60,7 +59,6 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
@Mock private lateinit var tileLayout: TileLayout
@Mock private lateinit var tileView: QSTileView
@Captor private lateinit var captor: ArgumentCaptor<QSPanel.OnConfigurationChangedListener>
- @Mock private lateinit var mediaCarouselController: MediaCarouselController
private val uiEventLogger = UiEventLoggerFake()
private val dumpManager = DumpManager()
@@ -90,8 +88,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager,
- mediaCarouselController)
+ dumpManager)
controller.init()
}
@@ -158,8 +155,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
metricsLogger: MetricsLogger,
uiEventLogger: UiEventLoggerFake,
qsLogger: QSLogger,
- dumpManager: DumpManager,
- mediaCarouselController: MediaCarouselController
+ dumpManager: DumpManager
) :
QuickQSPanelController(
view,
@@ -171,8 +167,7 @@ class QuickQSPanelControllerTest : SysuiTestCase() {
metricsLogger,
uiEventLogger,
qsLogger,
- dumpManager,
- mediaCarouselController) {
+ dumpManager) {
private var rotation = RotationUtils.ROTATION_NONE
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
index eb907bd92471..39d89bf99af2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt
@@ -110,7 +110,7 @@ class QuickStatusBarHeaderControllerTest : SysuiTestCase() {
`when`(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
`when`(variableDateViewControllerFactory.create(any()))
.thenReturn(variableDateViewController)
- `when`(iconManagerFactory.create(any())).thenReturn(iconManager)
+ `when`(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
`when`(view.resources).thenReturn(mContext.resources)
`when`(view.isAttachedToWindow).thenReturn(true)
`when`(view.context).thenReturn(context)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
index c4485389d646..c76d9e7a2b20 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -176,7 +176,7 @@ class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
- whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
+ whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 5ecfc8eb3649..90ae693db955 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -97,7 +97,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
whenever(variableDateViewControllerFactory.create(any()))
.thenReturn(variableDateViewController)
- whenever(iconManagerFactory.create(any())).thenReturn(iconManager)
+ whenever(iconManagerFactory.create(any(), any())).thenReturn(iconManager)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
view,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index 131eac668af3..8be138a3b2be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -113,7 +113,7 @@ class ClockRegistryTest : SysuiTestCase() {
registry.isEnabled = true
verify(mockPluginManager)
- .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java))
+ .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java), eq(true))
pluginListener = captor.value
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index ba5f5038c1d9..cfaa4707ef76 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -135,7 +135,7 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
- when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+ when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
allowTestableLooperAsMainThread();
TestableLooper.get(this).runWithLooper(() -> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index de7db74495af..34399b80c9f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -51,8 +51,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import javax.inject.Provider;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
@@ -79,8 +77,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
LinearLayout layout = new LinearLayout(mContext);
TestDarkIconManager manager = new TestDarkIconManager(
layout,
+ StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- () -> mock(WifiViewModel.class),
+ mock(WifiViewModel.class),
mMobileContextProvider,
mock(DarkIconDispatcher.class));
testCallOnAdd_forManager(manager);
@@ -121,13 +120,15 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
TestDarkIconManager(
LinearLayout group,
+ StatusBarLocation location,
StatusBarPipelineFlags statusBarPipelineFlags,
- Provider<WifiViewModel> wifiViewModelProvider,
+ WifiViewModel wifiViewModel,
MobileContextProvider contextProvider,
DarkIconDispatcher darkIconDispatcher) {
super(group,
+ location,
statusBarPipelineFlags,
- wifiViewModelProvider,
+ wifiViewModel,
contextProvider,
darkIconDispatcher);
}
@@ -165,8 +166,9 @@ public class StatusBarIconControllerTest extends LeakCheckedTest {
private static class TestIconManager extends IconManager implements TestableIconManager {
TestIconManager(ViewGroup group, MobileContextProvider contextProvider) {
super(group,
+ StatusBarLocation.HOME,
mock(StatusBarPipelineFlags.class),
- () -> mock(WifiViewModel.class),
+ mock(WifiViewModel.class),
contextProvider);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index faadd240edad..1ce4d619123c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -428,7 +428,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
mOperatorNameViewControllerFactory = mock(OperatorNameViewController.Factory.class);
when(mOperatorNameViewControllerFactory.create(any()))
.thenReturn(mOperatorNameViewController);
- when(mIconManagerFactory.create(any())).thenReturn(mIconManager);
+ when(mIconManagerFactory.create(any(), any())).thenReturn(mIconManager);
mSecureSettings = mock(SecureSettings.class);
setUpNotificationIconAreaController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
index 36be1be309d6..0e75c74ef6f5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityPipelineLoggerTest.kt
@@ -23,9 +23,16 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.log.LogBufferFactory
import com.android.systemui.log.LogcatEchoTracker
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logInputChange
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
import com.google.common.truth.Truth.assertThat
import java.io.PrintWriter
import java.io.StringWriter
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.mock
@@ -64,12 +71,70 @@ class ConnectivityPipelineLoggerTest : SysuiTestCase() {
assertThat(actualString).contains(expectedNetId)
}
- private val NET_1_ID = 100
- private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
- Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+ @Test
+ fun logOutputChange_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
+ val flow: Flow<Int?> = flowOf(1, null, 3)
+
+ val job = flow
+ .logOutputChange(logger, "testInts")
+ .launchIn(this)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains("1")
+ assertThat(actualString).contains("null")
+ assertThat(actualString).contains("3")
+
+ job.cancel()
+ }
+
+ @Test
+ fun logInputChange_unit_printsInputName() = runBlocking(IMMEDIATE) {
+ val flow: Flow<Unit> = flowOf(Unit, Unit)
+
+ val job = flow
+ .logInputChange(logger, "testInputs")
+ .launchIn(this)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains("testInputs")
+
+ job.cancel()
+ }
+
+ @Test
+ fun logInputChange_any_printsValuesAndNulls() = runBlocking(IMMEDIATE) {
+ val flow: Flow<Any?> = flowOf(null, 2, "threeString")
+
+ val job = flow
+ .logInputChange(logger, "testInputs")
+ .launchIn(this)
+
+ val stringWriter = StringWriter()
+ buffer.dump(PrintWriter(stringWriter), tailLength = 0)
+ val actualString = stringWriter.toString()
+
+ assertThat(actualString).contains("null")
+ assertThat(actualString).contains("2")
+ assertThat(actualString).contains("threeString")
+
+ job.cancel()
+ }
+
+ companion object {
+ private const val NET_1_ID = 100
+ private val NET_1 = com.android.systemui.util.mockito.mock<Network>().also {
+ Mockito.`when`(it.getNetId()).thenReturn(NET_1_ID)
+ }
+ private val NET_1_CAPS = NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build()
+ private val IMMEDIATE = Dispatchers.Main.immediate
}
- private val NET_1_CAPS = NetworkCapabilities.Builder()
- .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
- .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
- .build()
}
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
index 6b8d4aa7c51f..f751afc195b2 100644
--- 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
@@ -16,20 +16,27 @@
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.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
-import kotlinx.coroutines.flow.Flow
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
/** Fake implementation of [WifiRepository] exposing set methods for all the flows. */
class FakeWifiRepository : WifiRepository {
+ private val _isWifiEnabled: MutableStateFlow<Boolean> = MutableStateFlow(false)
+ override val isWifiEnabled: StateFlow<Boolean> = _isWifiEnabled
+
private val _wifiNetwork: MutableStateFlow<WifiNetworkModel> =
MutableStateFlow(WifiNetworkModel.Inactive)
- override val wifiNetwork: Flow<WifiNetworkModel> = _wifiNetwork
+ override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
- override val wifiActivity: Flow<WifiActivityModel> = _wifiActivity
+ override val wifiActivity: StateFlow<WifiActivityModel> = _wifiActivity
+
+ fun setIsWifiEnabled(enabled: Boolean) {
+ _isWifiEnabled.value = enabled
+ }
fun setWifiNetwork(wifiNetworkModel: WifiNetworkModel) {
_wifiNetwork.value = wifiNetworkModel
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
index d070ba0e47be..0ba0bd623c39 100644
--- 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
@@ -28,15 +28,17 @@ 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.broadcast.BroadcastDispatcher
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.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
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.mockito.mock
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import java.util.concurrent.Executor
@@ -44,23 +46,28 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Before
import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiRepositoryImplTest : SysuiTestCase() {
private lateinit var underTest: WifiRepositoryImpl
+ @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
@Mock private lateinit var logger: ConnectivityPipelineLogger
@Mock private lateinit var connectivityManager: ConnectivityManager
@Mock private lateinit var wifiManager: WifiManager
@@ -70,16 +77,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ ).thenReturn(flowOf(Unit))
executor = FakeExecutor(FakeSystemClock())
scope = CoroutineScope(IMMEDIATE)
-
- underTest = WifiRepositoryImpl(
- connectivityManager,
- logger,
- executor,
- scope,
- wifiManager,
- )
+ underTest = createRepo()
}
@After
@@ -88,6 +96,132 @@ class WifiRepositoryImplTest : SysuiTestCase() {
}
@Test
+ fun isWifiEnabled_nullWifiManager_getsFalse() = runBlocking(IMMEDIATE) {
+ underTest = createRepo(wifiManagerToUse = null)
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+ }
+
+ @Test
+ fun isWifiEnabled_initiallyGetsWifiManagerValue() = runBlocking(IMMEDIATE) {
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+
+ underTest = createRepo()
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+ }
+
+ @Test
+ fun isWifiEnabled_networkCapabilitiesChanged_valueUpdated() = runBlocking(IMMEDIATE) {
+ // We need to call launch on the flows so that they start updating
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback().onCapabilitiesChanged(
+ NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+ )
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback().onCapabilitiesChanged(
+ NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+ )
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_networkLost_valueUpdated() = runBlocking(IMMEDIATE) {
+ // We need to call launch on the flows so that they start updating
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback().onLost(NETWORK)
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback().onLost(NETWORK)
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_intentsReceived_valueUpdated() = runBlocking(IMMEDIATE) {
+ val intentFlow = MutableSharedFlow<Unit>()
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ ).thenReturn(intentFlow)
+ underTest = createRepo()
+
+ val job = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ intentFlow.emit(Unit)
+
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ intentFlow.emit(Unit)
+
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ job.cancel()
+ }
+
+ @Test
+ fun isWifiEnabled_bothIntentAndNetworkUpdates_valueAlwaysUpdated() = runBlocking(IMMEDIATE) {
+ val intentFlow = MutableSharedFlow<Unit>()
+ whenever(
+ broadcastDispatcher.broadcastFlow(
+ any(),
+ nullable(),
+ anyInt(),
+ nullable(),
+ )
+ ).thenReturn(intentFlow)
+ underTest = createRepo()
+
+ val networkJob = underTest.wifiNetwork.launchIn(this)
+ val enabledJob = underTest.isWifiEnabled.launchIn(this)
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ intentFlow.emit(Unit)
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ getNetworkCallback().onLost(NETWORK)
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(false)
+ getNetworkCallback().onCapabilitiesChanged(
+ NETWORK, createWifiNetworkCapabilities(PRIMARY_WIFI_INFO)
+ )
+ assertThat(underTest.isWifiEnabled.value).isFalse()
+
+ whenever(wifiManager.isWifiEnabled).thenReturn(true)
+ intentFlow.emit(Unit)
+ assertThat(underTest.isWifiEnabled.value).isTrue()
+
+ networkJob.cancel()
+ enabledJob.cancel()
+ }
+
+ @Test
fun wifiNetwork_initiallyGetsDefault() = runBlocking(IMMEDIATE) {
var latest: WifiNetworkModel? = null
val job = underTest
@@ -509,13 +643,7 @@ class WifiRepositoryImplTest : SysuiTestCase() {
@Test
fun wifiActivity_nullWifiManager_receivesDefault() = runBlocking(IMMEDIATE) {
- underTest = WifiRepositoryImpl(
- connectivityManager,
- logger,
- executor,
- scope,
- wifiManager = null,
- )
+ underTest = createRepo(wifiManagerToUse = null)
var latest: WifiActivityModel? = null
val job = underTest
@@ -594,6 +722,17 @@ class WifiRepositoryImplTest : SysuiTestCase() {
job.cancel()
}
+ private fun createRepo(wifiManagerToUse: WifiManager? = wifiManager): WifiRepositoryImpl {
+ return WifiRepositoryImpl(
+ broadcastDispatcher,
+ connectivityManager,
+ logger,
+ executor,
+ scope,
+ wifiManagerToUse,
+ )
+ }
+
private fun getTrafficStateCallback(): TrafficStateCallback {
val callbackCaptor = argumentCaptor<TrafficStateCallback>()
verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
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
index e896749d9a94..39b886af1cb8 100644
--- 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
@@ -16,13 +16,14 @@
package com.android.systemui.statusbar.pipeline.wifi.domain.interactor
+import android.net.wifi.WifiManager
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -50,172 +51,129 @@ class WifiInteractorTest : SysuiTestCase() {
}
@Test
- fun hasActivityIn_noInOrOut_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
- )
+ fun ssid_inactiveNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
- var latest: Boolean? = null
+ var latest: String? = "default"
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isNull()
job.cancel()
}
@Test
- fun hasActivityIn_onlyOut_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
- )
+ fun ssid_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
- var latest: Boolean? = null
+ var latest: String? = "default"
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isNull()
job.cancel()
}
@Test
- fun hasActivityIn_onlyIn_outputsTrue() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
-
- var latest: Boolean? = null
+ fun ssid_isPasspointAccessPoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+ networkId = 1,
+ isPasspointAccessPoint = true,
+ passpointProviderFriendlyName = "friendly",
+ ))
+
+ var latest: String? = null
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
- assertThat(latest).isTrue()
+ assertThat(latest).isEqualTo("friendly")
job.cancel()
}
@Test
- fun hasActivityIn_inAndOut_outputsTrue() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
-
- var latest: Boolean? = null
+ fun ssid_isOnlineSignUpForPasspoint_outputsPasspointName() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+ networkId = 1,
+ isOnlineSignUpForPasspointAccessPoint = true,
+ passpointProviderFriendlyName = "friendly",
+ ))
+
+ var latest: String? = null
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .ssid
+ .onEach { latest = it }
+ .launchIn(this)
- assertThat(latest).isTrue()
+ assertThat(latest).isEqualTo("friendly")
job.cancel()
}
@Test
- fun hasActivityIn_ssidNull_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = 1, ssid = null))
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
+ fun ssid_unknownSsid_outputsNull() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+ networkId = 1,
+ ssid = WifiManager.UNKNOWN_SSID,
+ ))
- var latest: Boolean? = null
+ var latest: String? = "default"
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
-
- assertThat(latest).isFalse()
-
- job.cancel()
- }
-
- @Test
- fun hasActivityIn_inactiveNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
-
- var latest: Boolean? = null
- val job = underTest
- .hasActivityIn
+ .ssid
.onEach { latest = it }
.launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isNull()
job.cancel()
}
@Test
- fun hasActivityIn_carrierMergedNetwork_outputsFalse() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
+ fun ssid_validSsid_outputsSsid() = runBlocking(IMMEDIATE) {
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(
+ networkId = 1,
+ ssid = "MyAwesomeWifiNetwork",
+ ))
- var latest: Boolean? = null
+ var latest: String? = null
val job = underTest
- .hasActivityIn
+ .ssid
.onEach { latest = it }
.launchIn(this)
- assertThat(latest).isFalse()
+ assertThat(latest).isEqualTo("MyAwesomeWifiNetwork")
job.cancel()
}
@Test
- fun hasActivityIn_multipleChanges_multipleOutputChanges() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(VALID_WIFI_NETWORK_MODEL)
-
+ fun isEnabled_matchesRepoIsEnabled() = runBlocking(IMMEDIATE) {
var latest: Boolean? = null
val job = underTest
- .hasActivityIn
- .onEach { latest = it }
- .launchIn(this)
+ .isEnabled
+ .onEach { latest = it }
+ .launchIn(this)
- // Conduct a series of changes and verify we catch each of them in succession
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
+ wifiRepository.setIsWifiEnabled(true)
yield()
assertThat(latest).isTrue()
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
- )
+ wifiRepository.setIsWifiEnabled(false)
yield()
assertThat(latest).isFalse()
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
- )
+ wifiRepository.setIsWifiEnabled(true)
yield()
assertThat(latest).isTrue()
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
- yield()
- assertThat(latest).isTrue()
-
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
- )
- yield()
- assertThat(latest).isFalse()
-
job.cancel()
}
@@ -242,6 +200,32 @@ class WifiInteractorTest : SysuiTestCase() {
}
@Test
+ fun activity_matchesRepoWifiActivity() = runBlocking(IMMEDIATE) {
+ var latest: WifiActivityModel? = null
+ val job = underTest
+ .activity
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity1 = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity1)
+ yield()
+ assertThat(latest).isEqualTo(activity1)
+
+ val activity2 = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity2)
+ yield()
+ assertThat(latest).isEqualTo(activity2)
+
+ val activity3 = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity3)
+ yield()
+ assertThat(latest).isEqualTo(activity3)
+
+ job.cancel()
+ }
+
+ @Test
fun isForceHidden_repoHasWifiHidden_outputsTrue() = runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
@@ -270,10 +254,6 @@ class WifiInteractorTest : SysuiTestCase() {
job.cancel()
}
-
- companion object {
- val VALID_WIFI_NETWORK_MODEL = WifiNetworkModel.Active(networkId = 1, ssid = "AB")
- }
}
private val IMMEDIATE = Dispatchers.Main.immediate
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index 3c200a5da4fa..4efb13520ebf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -16,38 +16,225 @@
package com.android.systemui.statusbar.pipeline.wifi.ui.view
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.testing.TestableLooper.RunWithLooper
+import android.testing.ViewUtils
+import android.view.View
import androidx.test.filters.SmallTest
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.lifecycle.InstantTaskExecutorRule
-import com.android.systemui.util.Assert
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
+import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
+import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
+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.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
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
@SmallTest
-@RunWith(JUnit4::class)
-@RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
class ModernStatusBarWifiViewTest : SysuiTestCase() {
+ private lateinit var testableLooper: TestableLooper
+
+ @Mock
+ private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
+ @Mock
+ private lateinit var logger: ConnectivityPipelineLogger
+ @Mock
+ private lateinit var connectivityConstants: ConnectivityConstants
+ @Mock
+ private lateinit var wifiConstants: WifiConstants
+ private lateinit var connectivityRepository: FakeConnectivityRepository
+ private lateinit var wifiRepository: FakeWifiRepository
+ private lateinit var interactor: WifiInteractor
+ private lateinit var viewModel: WifiViewModel
+ private lateinit var scope: CoroutineScope
+
@JvmField @Rule
val instantTaskExecutor = InstantTaskExecutorRule()
@Before
fun setUp() {
- Assert.setTestThread(Thread.currentThread())
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+
+ connectivityRepository = FakeConnectivityRepository()
+ wifiRepository = FakeWifiRepository()
+ wifiRepository.setIsWifiEnabled(true)
+ interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ scope = CoroutineScope(Dispatchers.Unconfined)
+ viewModel = WifiViewModel(
+ connectivityConstants,
+ context,
+ logger,
+ interactor,
+ scope,
+ statusBarPipelineFlags,
+ wifiConstants,
+ )
}
@Test
fun constructAndBind_hasCorrectSlot() {
val view = ModernStatusBarWifiView.constructAndBind(
- context, "slotName", mock()
+ context, "slotName", viewModel, StatusBarLocation.HOME
)
assertThat(view.slot).isEqualTo("slotName")
}
+
+ @Test
+ fun getVisibleState_icon_returnsIcon() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_ICON)
+ }
+
+ @Test
+ fun getVisibleState_dot_returnsDot() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_DOT, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_DOT)
+ }
+
+ @Test
+ fun getVisibleState_hidden_returnsHidden() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+
+ assertThat(view.visibleState).isEqualTo(STATE_HIDDEN)
+ }
+
+ // Note: The following tests are more like integration tests, since they stand up a full
+ // [WifiViewModel] and test the interactions between the view, view-binder, and view-model.
+
+ @Test
+ fun setVisibleState_icon_iconShownDotHidden() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_ICON, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconGroupView().visibility).isEqualTo(View.VISIBLE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setVisibleState_dot_iconHiddenDotShown() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_DOT, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.VISIBLE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun setVisibleState_hidden_iconAndDotHidden() {
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.getIconGroupView().visibility).isEqualTo(View.GONE)
+ assertThat(view.getDotView().visibility).isEqualTo(View.GONE)
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_notEnabled_outputsFalse() {
+ wifiRepository.setIsWifiEnabled(false)
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
+ )
+
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isFalse()
+
+ ViewUtils.detachView(view)
+ }
+
+ @Test
+ fun isIconVisible_enabled_outputsTrue() {
+ wifiRepository.setIsWifiEnabled(true)
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
+ )
+
+ val view = ModernStatusBarWifiView.constructAndBind(
+ context, SLOT_NAME, viewModel, StatusBarLocation.HOME
+ )
+
+ ViewUtils.attachView(view)
+ testableLooper.processAllMessages()
+
+ assertThat(view.isIconVisible).isTrue()
+
+ ViewUtils.detachView(view)
+ }
+
+ private fun View.getIconGroupView(): View {
+ return this.requireViewById(R.id.wifi_group)
+ }
+
+ private fun View.getDotView(): View {
+ return this.requireViewById(R.id.status_bar_dot)
+ }
}
+
+private const val SLOT_NAME = "TestSlotName"
+private const val NETWORK_ID = 200
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
index 43103a065e68..74ea21c11447 100644
--- 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
@@ -26,28 +26,33 @@ import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS
import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK
import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants
import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot
import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository
-import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.data.model.WifiNetworkModel
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.android.systemui.statusbar.pipeline.wifi.shared.model.WifiActivityModel
import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel.Companion.NO_INTERNET
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
class WifiViewModelTest : SysuiTestCase() {
@@ -56,38 +61,72 @@ class WifiViewModelTest : SysuiTestCase() {
@Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags
@Mock private lateinit var logger: ConnectivityPipelineLogger
- @Mock private lateinit var constants: WifiConstants
+ @Mock private lateinit var connectivityConstants: ConnectivityConstants
+ @Mock private lateinit var wifiConstants: WifiConstants
private lateinit var connectivityRepository: FakeConnectivityRepository
private lateinit var wifiRepository: FakeWifiRepository
private lateinit var interactor: WifiInteractor
+ private lateinit var scope: CoroutineScope
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
connectivityRepository = FakeConnectivityRepository()
wifiRepository = FakeWifiRepository()
+ wifiRepository.setIsWifiEnabled(true)
interactor = WifiInteractor(connectivityRepository, wifiRepository)
+ scope = CoroutineScope(IMMEDIATE)
+ createAndSetViewModel()
+ }
- underTest = WifiViewModel(
- statusBarPipelineFlags,
- constants,
- context,
- logger,
- interactor
- )
+ @After
+ fun tearDown() {
+ scope.cancel()
+ }
+
+ // Note on testing: [WifiViewModel] exposes 3 different instances of
+ // [LocationBasedWifiViewModel]. In practice, these 3 different instances will get the exact
+ // same data for icon, activity, etc. flows. So, most of these tests will test just one of the
+ // instances. There are also some tests that verify all 3 instances received the same data.
+
+ // TODO(b/238425913): We should probably parameterize the wifiIcon tests since there's so many
+ // different possibilities.
+
+ @Test
+ fun wifiIcon_notEnabled_outputsNull() = runBlocking(IMMEDIATE) {
+ wifiRepository.setIsWifiEnabled(false)
+
+ // Start as non-null so we can verify we got the update
+ var latest: Icon? = Icon.Resource(0, null)
+ val job = underTest
+ .home
+ .wifiIcon
+ .onEach { latest = it }
+ .launchIn(this)
+
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
}
@Test
fun wifiIcon_forceHidden_outputsNull() = runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf(ConnectivitySlot.WIFI))
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
- var latest: Icon? = null
+ // Start as non-null so we can verify we got the update
+ var latest: Icon? = Icon.Resource(0, null)
val job = underTest
+ .home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
+ yield()
+
assertThat(latest).isNull()
job.cancel()
@@ -96,28 +135,60 @@ class WifiViewModelTest : SysuiTestCase() {
@Test
fun wifiIcon_notForceHidden_outputsVisible() = runBlocking(IMMEDIATE) {
connectivityRepository.setForceHiddenIcons(setOf())
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = 2))
var latest: Icon? = null
val job = underTest
+ .home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
+ )
+ yield()
+
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
job.cancel()
}
@Test
- fun wifiIcon_inactiveNetwork_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
+ fun wifiIcon_inactiveNetwork_alwaysShowFalse_outputsNull() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.alwaysShowIconIfEnabled).thenReturn(false)
+ whenever(connectivityConstants.hasDataCapabilities).thenReturn(true)
+ createAndSetViewModel()
+
+ // Start as non-null so we can verify we got the update
+ var latest: Icon? = Icon.Resource(0, null)
+ val job = underTest
+ .home
+ .wifiIcon
+ .onEach { latest = it }
+ .launchIn(this)
+
wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiIcon_inactiveNetwork_alwaysShowTrue_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.alwaysShowIconIfEnabled).thenReturn(true)
+ createAndSetViewModel()
var latest: Icon? = null
val job = underTest
- .wifiIcon
- .onEach { latest = it }
- .launchIn(this)
+ .home
+ .wifiIcon
+ .onEach { latest = it }
+ .launchIn(this)
+
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+ yield()
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
val icon = latest as Icon.Resource
@@ -131,15 +202,70 @@ class WifiViewModelTest : SysuiTestCase() {
}
@Test
- fun wifiIcon_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+ fun wifiIcon_inactiveNetwork_hasDataCaps_outputsNull() = runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.hasDataCapabilities).thenReturn(true)
+ createAndSetViewModel()
+
+ // Start as non-null so we can verify we got the update
+ var latest: Icon? = Icon.Resource(0, null)
+ val job = underTest
+ .home
+ .wifiIcon
+ .onEach { latest = it }
+ .launchIn(this)
+
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiIcon_inactiveNetwork_noDataCaps_outputsNoNetworkIcon() = runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.hasDataCapabilities).thenReturn(false)
+ createAndSetViewModel()
var latest: Icon? = null
val job = underTest
+ .home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
+ yield()
+
+ assertThat(latest).isInstanceOf(Icon.Resource::class.java)
+ val icon = latest as Icon.Resource
+ assertThat(icon.res).isEqualTo(WIFI_NO_NETWORK)
+ assertThat(icon.contentDescription?.getAsString())
+ .contains(context.getString(WIFI_NO_CONNECTION))
+ assertThat(icon.contentDescription?.getAsString())
+ .contains(context.getString(NO_INTERNET))
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiIcon_carrierMergedNetwork_outputsNull() = runBlocking(IMMEDIATE) {
+ // Even when we should always show the icon
+ whenever(wifiConstants.alwaysShowIconIfEnabled).thenReturn(true)
+ createAndSetViewModel()
+
+ var latest: Icon? = Icon.Resource(0, null)
+ val job = underTest
+ .home
+ .wifiIcon
+ .onEach { latest = it }
+ .launchIn(this)
+
+ // WHEN we have a carrier merged network
+ wifiRepository.setWifiNetwork(WifiNetworkModel.CarrierMerged)
+ yield()
+
+ // THEN we override the alwaysShow boolean and still don't show the icon
assertThat(latest).isNull()
job.cancel()
@@ -147,14 +273,22 @@ class WifiViewModelTest : SysuiTestCase() {
@Test
fun wifiIcon_isActiveNullLevel_outputsNull() = runBlocking(IMMEDIATE) {
- wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
+ // Even when we should always show the icon
+ whenever(wifiConstants.alwaysShowIconIfEnabled).thenReturn(true)
+ createAndSetViewModel()
- var latest: Icon? = null
+ var latest: Icon? = Icon.Resource(0, null)
val job = underTest
+ .home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
+ // WHEN we have a null level
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, level = null))
+ yield()
+
+ // THEN we override the alwaysShow boolean and still don't show the icon
assertThat(latest).isNull()
job.cancel()
@@ -162,51 +296,134 @@ class WifiViewModelTest : SysuiTestCase() {
@Test
fun wifiIcon_isActiveAndValidated_level1_outputsFull1Icon() = runBlocking(IMMEDIATE) {
+ var latest: Icon? = null
+ val job = underTest
+ .home
+ .wifiIcon
+ .onEach { latest = it }
+ .launchIn(this)
+
val level = 1
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ isValidated = true,
+ level,
+ )
+ )
+ yield()
+
+ assertThat(latest).isInstanceOf(Icon.Resource::class.java)
+ val icon = latest as Icon.Resource
+ assertThat(icon.res).isEqualTo(WIFI_FULL_ICONS[level])
+ assertThat(icon.contentDescription?.getAsString())
+ .contains(context.getString(WIFI_CONNECTION_STRENGTH[level]))
+ assertThat(icon.contentDescription?.getAsString())
+ .doesNotContain(context.getString(NO_INTERNET))
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiIcon_isActiveAndNotValidated_alwaysShowFalse_outputsNull() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.alwaysShowIconIfEnabled).thenReturn(false)
+ whenever(connectivityConstants.hasDataCapabilities).thenReturn(true)
+ createAndSetViewModel()
+
+ var latest: Icon? = Icon.Resource(0, null)
+ val job = underTest
+ .home
+ .wifiIcon
+ .onEach { latest = it }
+ .launchIn(this)
wifiRepository.setWifiNetwork(
- WifiNetworkModel.Active(
- NETWORK_ID,
- isValidated = true,
- level = level
- )
+ WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 4,)
)
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiIcon_isActiveAndNotValidated_alwaysShowTrue_outputsIcon() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.alwaysShowIconIfEnabled).thenReturn(true)
+ createAndSetViewModel()
var latest: Icon? = null
val job = underTest
+ .home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
+ val level = 4
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ isValidated = false,
+ level,
+ )
+ )
+ yield()
+
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
val icon = latest as Icon.Resource
- assertThat(icon.res).isEqualTo(WIFI_FULL_ICONS[level])
+ assertThat(icon.res).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
assertThat(icon.contentDescription?.getAsString())
.contains(context.getString(WIFI_CONNECTION_STRENGTH[level]))
assertThat(icon.contentDescription?.getAsString())
- .doesNotContain(context.getString(NO_INTERNET))
+ .contains(context.getString(NO_INTERNET))
job.cancel()
}
@Test
- fun wifiIcon_isActiveAndNotValidated_level4_outputsEmpty4Icon() = runBlocking(IMMEDIATE) {
- val level = 4
+ fun wifiIcon_isActiveAndNotValidated_hasDataCaps_outputsNull() = runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.hasDataCapabilities).thenReturn(true)
+ createAndSetViewModel()
+
+ var latest: Icon? = Icon.Resource(0, null)
+ val job = underTest
+ .home
+ .wifiIcon
+ .onEach { latest = it }
+ .launchIn(this)
wifiRepository.setWifiNetwork(
- WifiNetworkModel.Active(
- NETWORK_ID,
- isValidated = false,
- level = level
- )
+ WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 4,)
)
+ yield()
+
+ assertThat(latest).isNull()
+
+ job.cancel()
+ }
+
+ @Test
+ fun wifiIcon_isActiveAndNotValidated_noDataCaps_outputsIcon() = runBlocking(IMMEDIATE) {
+ whenever(connectivityConstants.hasDataCapabilities).thenReturn(false)
+ createAndSetViewModel()
var latest: Icon? = null
val job = underTest
+ .home
.wifiIcon
.onEach { latest = it }
.launchIn(this)
+ val level = 4
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ isValidated = false,
+ level,
+ )
+ )
+ yield()
+
assertThat(latest).isInstanceOf(Icon.Resource::class.java)
val icon = latest as Icon.Resource
assertThat(icon.res).isEqualTo(WIFI_NO_INTERNET_ICONS[level])
@@ -219,68 +436,399 @@ class WifiViewModelTest : SysuiTestCase() {
}
@Test
- fun activityInVisible_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
- whenever(constants.shouldShowActivityConfig).thenReturn(false)
+ fun wifiIcon_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
+ var latestHome: Icon? = null
+ val jobHome = underTest
+ .home
+ .wifiIcon
+ .onEach { latestHome = it }
+ .launchIn(this)
+
+ var latestKeyguard: Icon? = null
+ val jobKeyguard = underTest
+ .keyguard
+ .wifiIcon
+ .onEach { latestKeyguard = it }
+ .launchIn(this)
+
+ var latestQs: Icon? = null
+ val jobQs = underTest
+ .qs
+ .wifiIcon
+ .onEach { latestQs = it }
+ .launchIn(this)
+
+ wifiRepository.setWifiNetwork(
+ WifiNetworkModel.Active(
+ NETWORK_ID,
+ isValidated = true,
+ level = 1
+ )
+ )
+ yield()
+
+ assertThat(latestHome).isInstanceOf(Icon.Resource::class.java)
+ assertThat(latestHome).isEqualTo(latestKeyguard)
+ assertThat(latestKeyguard).isEqualTo(latestQs)
+
+ jobHome.cancel()
+ jobKeyguard.cancel()
+ jobQs.cancel()
+ }
+
+ @Test
+ fun activity_showActivityConfigFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var activityIn: Boolean? = null
+ val activityInJob = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { activityIn = it }
+ .launchIn(this)
+
+ var activityOut: Boolean? = null
+ val activityOutJob = underTest
+ .home
+ .isActivityOutViewVisible
+ .onEach { activityOut = it }
+ .launchIn(this)
+
+ var activityContainer: Boolean? = null
+ val activityContainerJob = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
+
+ // Verify that on launch, we receive false.
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
+
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
+ }
+
+ @Test
+ fun activity_showActivityConfigFalse_noUpdatesReceived() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(false)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var activityIn: Boolean? = null
+ val activityInJob = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { activityIn = it }
+ .launchIn(this)
+
+ var activityOut: Boolean? = null
+ val activityOutJob = underTest
+ .home
+ .isActivityOutViewVisible
+ .onEach { activityOut = it }
+ .launchIn(this)
+
+ var activityContainer: Boolean? = null
+ val activityContainerJob = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
+
+ // WHEN we update the repo to have activity
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ // THEN we didn't update to the new activity (because our config is false)
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
+
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
+ }
+
+ @Test
+ fun activity_nullSsid_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+
+ wifiRepository.setWifiNetwork(WifiNetworkModel.Active(NETWORK_ID, ssid = null))
+
+ var activityIn: Boolean? = null
+ val activityInJob = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { activityIn = it }
+ .launchIn(this)
+
+ var activityOut: Boolean? = null
+ val activityOutJob = underTest
+ .home
+ .isActivityOutViewVisible
+ .onEach { activityOut = it }
+ .launchIn(this)
+
+ var activityContainer: Boolean? = null
+ val activityContainerJob = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { activityContainer = it }
+ .launchIn(this)
+
+ // WHEN we update the repo to have activity
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ // THEN we still output false because our network's SSID is null
+ assertThat(activityIn).isFalse()
+ assertThat(activityOut).isFalse()
+ assertThat(activityContainer).isFalse()
+
+ activityInJob.cancel()
+ activityOutJob.cancel()
+ activityContainerJob.cancel()
+ }
+
+ @Test
+ fun activity_allLocationViewModelsReceiveSameData() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var latestHome: Boolean? = null
+ val jobHome = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { latestHome = it }
+ .launchIn(this)
+
+ var latestKeyguard: Boolean? = null
+ val jobKeyguard = underTest
+ .keyguard
+ .isActivityInViewVisible
+ .onEach { latestKeyguard = it }
+ .launchIn(this)
+
+ var latestQs: Boolean? = null
+ val jobQs = underTest
+ .qs
+ .isActivityInViewVisible
+ .onEach { latestQs = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latestHome).isTrue()
+ assertThat(latestKeyguard).isTrue()
+ assertThat(latestQs).isTrue()
+
+ jobHome.cancel()
+ jobKeyguard.cancel()
+ jobQs.cancel()
+ }
+
+ @Test
+ fun activityIn_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
- .isActivityInVisible
- .onEach { latest = it }
- .launchIn(this)
+ .home
+ .isActivityInViewVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activityIn_hasActivityInFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .home
+ .isActivityInViewVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
- // 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)
+ fun activityOut_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
- .isActivityInVisible
- .onEach { latest = it }
- .launchIn(this)
+ .home
+ .isActivityOutViewVisible
+ .onEach { latest = it }
+ .launchIn(this)
- // Update the repo to have activityIn
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
+ val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activityOut_hasActivityOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .home
+ .isActivityOutViewVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
yield()
- // Verify that we didn't update to activityIn=true (because our config is false)
assertThat(latest).isFalse()
job.cancel()
}
@Test
- fun activityInVisible_showActivityConfigTrue_outputsUpdate() = runBlocking(IMMEDIATE) {
- whenever(constants.shouldShowActivityConfig).thenReturn(true)
+ fun activityContainer_hasActivityInTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
var latest: Boolean? = null
val job = underTest
- .isActivityInVisible
- .onEach { latest = it }
- .launchIn(this)
+ .home
+ .isActivityContainerVisible
+ .onEach { latest = it }
+ .launchIn(this)
- // Update the repo to have activityIn
- wifiRepository.setWifiActivity(
- WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
- )
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
yield()
- // Verify that we updated to activityIn=true
assertThat(latest).isTrue()
job.cancel()
}
+ @Test
+ fun activityContainer_hasActivityOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activityContainer_inAndOutTrue_outputsTrue() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = true, hasActivityOut = true)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isTrue()
+
+ job.cancel()
+ }
+
+ @Test
+ fun activityContainer_inAndOutFalse_outputsFalse() = runBlocking(IMMEDIATE) {
+ whenever(wifiConstants.shouldShowActivityConfig).thenReturn(true)
+ createAndSetViewModel()
+ wifiRepository.setWifiNetwork(ACTIVE_VALID_WIFI_NETWORK)
+
+ var latest: Boolean? = null
+ val job = underTest
+ .home
+ .isActivityContainerVisible
+ .onEach { latest = it }
+ .launchIn(this)
+
+ val activity = WifiActivityModel(hasActivityIn = false, hasActivityOut = false)
+ wifiRepository.setWifiActivity(activity)
+ yield()
+
+ assertThat(latest).isFalse()
+
+ job.cancel()
+ }
+
+ private fun createAndSetViewModel() {
+ // [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow
+ // creations rely on certain config values that we mock out in individual tests. This method
+ // allows tests to create the view model only after those configs are correctly set up.
+ underTest = WifiViewModel(
+ connectivityConstants,
+ context,
+ logger,
+ interactor,
+ scope,
+ statusBarPipelineFlags,
+ wifiConstants,
+ )
+ }
+
private fun ContentDescription.getAsString(): String? {
return when (this) {
is ContentDescription.Loaded -> this.description
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index c47ea9cea75e..6ace4044b3f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -275,10 +275,9 @@ public class RemoteInputViewTest extends SysuiTestCase {
EditText editText = view.findViewById(R.id.remote_input_text);
editText.setText(TEST_REPLY);
ClipDescription description = new ClipDescription("", new String[] {"image/png"});
- // We need to use an (arbitrary) real resource here so that an actual image gets attached.
+ // We need to use an (arbitrary) real resource here so that an actual image gets attached
ClipData clip = new ClipData(description, new ClipData.Item(
- Uri.parse("android.resource://com.android.systemui/"
- + R.drawable.default_thumbnail)));
+ Uri.parse("android.resource://android/" + android.R.drawable.btn_default)));
ContentInfo payload =
new ContentInfo.Builder(clip, SOURCE_CLIPBOARD).build();
view.setAttachment(payload);
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 8d94f9545b59..f17f8f7bd7e4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -731,7 +731,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
intentFilter.addAction(Intent.ACTION_USER_REMOVED);
- intentFilter.addAction(Intent.ACTION_USER_PRESENT);
intentFilter.addAction(Intent.ACTION_SETTING_RESTORED);
mContext.registerReceiverAsUser(new BroadcastReceiver() {
@@ -749,14 +748,6 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
unlockUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
- // We will update when the automation service dies.
- synchronized (mLock) {
- AccessibilityUserState userState = getCurrentUserStateLocked();
- if (readConfigurationForUserStateLocked(userState)) {
- onUserStateChangedLocked(userState);
- }
- }
} else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
final String which = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
if (Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES.equals(which)) {
diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java
index 838cbd9079d6..ec30369bd099 100644
--- a/services/companion/java/com/android/server/companion/virtual/InputController.java
+++ b/services/companion/java/com/android/server/companion/virtual/InputController.java
@@ -24,7 +24,6 @@ import android.graphics.PointF;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
@@ -42,6 +41,7 @@ import android.view.WindowManager;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index d98dffbe1f56..7d85c1333b1b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -22,6 +22,7 @@ import static android.Manifest.permission.FILTER_EVENTS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
+import static android.Manifest.permission.MANAGE_USERS;
import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
import static android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND;
import static android.app.ActivityManager.INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
@@ -245,6 +246,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.PermissionInfo;
+import android.content.pm.PermissionMethod;
import android.content.pm.ProcessInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ProviderInfoList;
@@ -257,6 +259,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
import android.media.audiofx.AudioEffect;
import android.net.ConnectivityManager;
@@ -331,6 +334,7 @@ import android.util.SparseIntArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;
+import android.view.Display;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
@@ -2365,6 +2369,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mUiHandler = injector.getUiHandler(null /* service */);
mUidObserverController = new UidObserverController(mUiHandler);
mUserController = new UserController(this);
+ mInjector.mUserController = mUserController;
mPendingIntentController =
new PendingIntentController(handlerThread.getLooper(), mUserController, mConstants);
mAppRestrictionController = new AppRestrictionController(mContext, this);
@@ -2479,6 +2484,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mUserController = new UserController(this);
+ mInjector.mUserController = mUserController;
mPendingIntentController = new PendingIntentController(
mHandlerThread.getLooper(), mUserController, mConstants);
@@ -5963,6 +5969,12 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ /**
+ * Allows if {@code pid} is {@link #MY_PID}, then denies if the {@code pid} has been denied
+ * provided non-{@code null} {@code permission} before. Otherwise calls into
+ * {@link ActivityManager#checkComponentPermission(String, int, int, boolean)}.
+ */
+ @PermissionMethod
public static int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
if (pid == MY_PID) {
@@ -6009,6 +6021,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* This can be called with or without the global lock held.
*/
@Override
+ @PermissionMethod
public int checkPermission(String permission, int pid, int uid) {
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
@@ -6020,6 +6033,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* Binder IPC calls go through the public entry point.
* This can be called with or without the global lock held.
*/
+ @PermissionMethod
int checkCallingPermission(String permission) {
return checkPermission(permission,
Binder.getCallingPid(),
@@ -6029,6 +6043,7 @@ public class ActivityManagerService extends IActivityManager.Stub
/**
* This can be called with or without the global lock held.
*/
+ @PermissionMethod
void enforceCallingPermission(String permission, String func) {
if (checkCallingPermission(permission)
== PackageManager.PERMISSION_GRANTED) {
@@ -6046,6 +6061,25 @@ public class ActivityManagerService extends IActivityManager.Stub
/**
* This can be called with or without the global lock held.
*/
+ @PermissionMethod
+ private void enforceCallingHasAtLeastOnePermission(String func, String... permissions) {
+ for (String permission : permissions) {
+ if (checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ }
+
+ String msg = "Permission Denial: " + func + " from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid()
+ + " requires one of " + Arrays.toString(permissions);
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ /**
+ * This can be called with or without the global lock held.
+ */
void enforcePermission(String permission, int pid, int uid, String func) {
if (checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED) {
return;
@@ -16322,8 +16356,34 @@ public class ActivityManagerService extends IActivityManager.Stub
@Override
public boolean startUserInBackgroundOnSecondaryDisplay(int userId, int displayId) {
+ int[] displayIds = getSecondaryDisplayIdsForStartingBackgroundUsers();
+ boolean validDisplay = false;
+ if (displayIds != null) {
+ for (int i = 0; i < displayIds.length; i++) {
+ if (displayId == displayIds[i]) {
+ validDisplay = true;
+ break;
+ }
+ }
+ }
+ if (!validDisplay) {
+ throw new IllegalArgumentException("Invalid display (" + displayId + ") to start user. "
+ + "Valid options are: " + Arrays.toString(displayIds));
+ }
+
+ if (DEBUG_MU) {
+ Slogf.d(TAG_MU, "Calling startUserOnSecondaryDisplay(%d, %d) using injector %s", userId,
+ displayId, mInjector);
+ }
// Permission check done inside UserController.
- return mUserController.startUserOnSecondaryDisplay(userId, displayId);
+ return mInjector.startUserOnSecondaryDisplay(userId, displayId);
+ }
+
+ @Override
+ public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+ enforceCallingHasAtLeastOnePermission("getSecondaryDisplayIdsForStartingBackgroundUsers()",
+ MANAGE_USERS, INTERACT_ACROSS_USERS);
+ return mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
}
/**
@@ -18332,8 +18392,10 @@ public class ActivityManagerService extends IActivityManager.Stub
@VisibleForTesting
public static class Injector {
+ private final Context mContext;
private NetworkManagementInternal mNmi;
- private Context mContext;
+
+ private UserController mUserController;
public Injector(Context context) {
mContext = context;
@@ -18359,6 +18421,103 @@ public class ActivityManagerService extends IActivityManager.Stub
}
/**
+ * Called by {@code AMS.getSecondaryDisplayIdsForStartingBackgroundUsers()}.
+ */
+ // NOTE: ideally Injector should have no complex logic, but if this logic was moved to AMS,
+ // it could not be tested with the existing ActivityManagerServiceTest (as DisplayManager,
+ // DisplayInfo, etc... are final and UserManager.isUsersOnSecondaryDisplaysEnabled is
+ // static).
+ // So, the logic was added here, and tested on ActivityManagerServiceInjectorTest (which
+ // was added on FrameworksMockingServicesTests and hence uses Extended Mockito to mock
+ // final and static stuff)
+ @Nullable
+ public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+ if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+ Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): not supported");
+ return null;
+ }
+
+ // NOTE: DisplayManagerInternal doesn't have a method to list all displays
+ DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+ Display[] allDisplays = displayManager.getDisplays();
+
+ // allDisplays should contain at least Display.DEFAULT_DISPLAY, but it's better to
+ // double check, just in case...
+ if (allDisplays == null || allDisplays.length == 0) {
+ Slogf.wtf(TAG, "displayManager (%s) returned no displays", displayManager);
+ return null;
+ }
+ boolean hasDefaultDisplay = false;
+ for (Display display : allDisplays) {
+ if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
+ hasDefaultDisplay = true;
+ break;
+ }
+ }
+ if (!hasDefaultDisplay) {
+ Slogf.wtf(TAG, "displayManager (%s) has %d displays (%s), but none has id "
+ + "DEFAULT_DISPLAY (%d)", displayManager, allDisplays.length,
+ Arrays.toString(allDisplays), Display.DEFAULT_DISPLAY);
+ return null;
+ }
+
+ // Starts with all displays but DEFAULT_DISPLAY
+ int[] displayIds = new int[allDisplays.length - 1];
+
+ // TODO(b/247592632): check for other properties like isSecure or proper display type
+ int numberValidDisplays = 0;
+ for (Display display : allDisplays) {
+ int displayId = display.getDisplayId();
+ if (display.isValid() && displayId != Display.DEFAULT_DISPLAY) {
+ displayIds[numberValidDisplays++] = displayId;
+ }
+ }
+
+ if (numberValidDisplays == 0) {
+ // TODO(b/247580038): remove this workaround once a virtual display on Car's
+ // KitchenSink (or other app) can be used while running CTS tests on devices that
+ // don't have a real display.
+ // STOPSHIP: if not removed, it should at least be unit tested
+ String testingProp = "fw.secondary_display_for_starting_users_for_testing_purposes";
+ int displayId = SystemProperties.getInt(testingProp, Display.DEFAULT_DISPLAY);
+ if (displayId != Display.DEFAULT_DISPLAY && displayId > 0) {
+ Slogf.w(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid "
+ + "display found, but returning %d as set by property %s", displayId,
+ testingProp);
+ return new int[] { displayId };
+ }
+ Slogf.e(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): no valid display"
+ + " on %s", Arrays.toString(allDisplays));
+ return null;
+ }
+
+ if (numberValidDisplays != displayIds.length) {
+ int[] validDisplayIds = new int[numberValidDisplays];
+ System.arraycopy(displayIds, 0, validDisplayIds, 0, numberValidDisplays);
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): returning "
+ + "only valid displays (%d instead of %d): %s", numberValidDisplays,
+ displayIds.length, Arrays.toString(validDisplayIds));
+ }
+ return validDisplayIds;
+ }
+
+ if (DEBUG_MU) {
+ Slogf.d(TAG, "getSecondaryDisplayIdsForStartingBackgroundUsers(): returning all "
+ + "(but DEFAULT_DISPLAY) displays : %s", Arrays.toString(displayIds));
+ }
+ return displayIds;
+ }
+
+ /**
+ * Called by {@code AMS.startUserOnSecondaryDisplay()}.
+ */
+ public boolean startUserOnSecondaryDisplay(int userId, int displayId) {
+ return mUserController.startUserOnSecondaryDisplay(userId, displayId);
+ }
+
+ /**
* Return the process list instance
*/
public ProcessList getProcessList(ActivityManagerService service) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index b4f6e35b3df3..10e2aae9a8d1 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -367,6 +367,8 @@ final class ActivityManagerShellCommand extends ShellCommand {
return runGetCurrentForegroundProcess(pw, mInternal, mTaskInterface);
case "reset-dropbox-rate-limiter":
return runResetDropboxRateLimiter();
+ case "list-secondary-displays-for-starting-users":
+ return runListSecondaryDisplaysForStartingUsers(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -2068,6 +2070,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
success = mInterface.startUserInBackgroundWithListener(userId, waiter);
displaySuffix = "";
} else {
+ if (!UserManager.isUsersOnSecondaryDisplaysEnabled()) {
+ pw.println("Not supported");
+ return -1;
+ }
success = mInterface.startUserInBackgroundOnSecondaryDisplay(userId, displayId);
displaySuffix = " on display " + displayId;
}
@@ -3591,6 +3597,14 @@ final class ActivityManagerShellCommand extends ShellCommand {
return 0;
}
+ int runListSecondaryDisplaysForStartingUsers(PrintWriter pw) throws RemoteException {
+ int[] displayIds = mInterface.getSecondaryDisplayIdsForStartingBackgroundUsers();
+ pw.println(displayIds == null || displayIds.length == 0
+ ? "none"
+ : Arrays.toString(displayIds));
+ return 0;
+ }
+
private Resources getResources(PrintWriter pw) throws RemoteException {
// system resources does not contain all the device configuration, construct it manually.
Configuration config = mInterface.getConfiguration();
@@ -3951,6 +3965,9 @@ final class ActivityManagerShellCommand extends ShellCommand {
pw.println(" Set an app's background restriction level which in turn map to a app standby bucket.");
pw.println(" get-bg-restriction-level [--user <USER_ID>] <PACKAGE>");
pw.println(" Get an app's background restriction level.");
+ pw.println(" list-secondary-displays-for-starting-users");
+ pw.println(" Lists the id of displays that can be used to start users on "
+ + "background.");
pw.println();
Intent.printIntentArgsHelp(pw, "");
}
diff --git a/services/core/java/com/android/server/am/BroadcastConstants.java b/services/core/java/com/android/server/am/BroadcastConstants.java
index 3efb628a8b75..f9b0dd0d6f28 100644
--- a/services/core/java/com/android/server/am/BroadcastConstants.java
+++ b/services/core/java/com/android/server/am/BroadcastConstants.java
@@ -137,6 +137,14 @@ public class BroadcastConstants {
private static final int DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS = 16;
/**
+ * For {@link BroadcastQueueModernImpl}: Maximum number of pending
+ * broadcasts to hold for a process before we ignore any delays that policy
+ * might have applied to that process.
+ */
+ public int MAX_PENDING_BROADCASTS = DEFAULT_MAX_PENDING_BROADCASTS;
+ private static final int DEFAULT_MAX_PENDING_BROADCASTS = 256;
+
+ /**
* For {@link BroadcastQueueModernImpl}: Default delay to apply to normal
* broadcasts, giving a chance for debouncing of rapidly changing events.
*/
@@ -217,6 +225,8 @@ public class BroadcastConstants {
DEFAULT_MAX_RUNNING_PROCESS_QUEUES);
MAX_RUNNING_ACTIVE_BROADCASTS = properties.getInt("bcast_max_running_active_broadcasts",
DEFAULT_MAX_RUNNING_ACTIVE_BROADCASTS);
+ MAX_PENDING_BROADCASTS = properties.getInt("bcast_max_pending_broadcasts",
+ DEFAULT_MAX_PENDING_BROADCASTS);
DELAY_NORMAL_MILLIS = properties.getLong("bcast_delay_normal_millis",
DEFAULT_DELAY_NORMAL_MILLIS);
DELAY_CACHED_MILLIS = properties.getLong("bcast_delay_cached_millis",
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 77eefb4e2743..342d1f2f3131 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -119,6 +119,9 @@ class BroadcastProcessQueue {
private boolean mProcessCached;
+ private String mCachedToString;
+ private String mCachedToShortString;
+
public BroadcastProcessQueue(@NonNull BroadcastConstants constants,
@NonNull String processName, int uid) {
this.constants = Objects.requireNonNull(constants);
@@ -327,7 +330,7 @@ class BroadcastProcessQueue {
}
public boolean isEmpty() {
- return (mActive != null) && mPending.isEmpty();
+ return mPending.isEmpty();
}
public boolean isActive() {
@@ -385,6 +388,12 @@ class BroadcastProcessQueue {
} else {
mRunnableAt = runnableAt + constants.DELAY_NORMAL_MILLIS;
}
+
+ // If we have too many broadcasts pending, bypass any delays that
+ // might have been applied above to aid draining
+ if (mPending.size() >= constants.MAX_PENDING_BROADCASTS) {
+ mRunnableAt = runnableAt;
+ }
} else {
mRunnableAt = Long.MAX_VALUE;
}
@@ -452,13 +461,19 @@ class BroadcastProcessQueue {
@Override
public String toString() {
- return "BroadcastProcessQueue{"
- + Integer.toHexString(System.identityHashCode(this))
- + " " + processName + "/" + UserHandle.formatUid(uid) + "}";
+ if (mCachedToString == null) {
+ mCachedToString = "BroadcastProcessQueue{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " " + processName + "/" + UserHandle.formatUid(uid) + "}";
+ }
+ return mCachedToString;
}
public String toShortString() {
- return processName + "/" + UserHandle.formatUid(uid);
+ if (mCachedToShortString == null) {
+ mCachedToShortString = processName + "/" + UserHandle.formatUid(uid);
+ }
+ return mCachedToShortString;
}
public void dumpLocked(@NonNull IndentingPrintWriter pw) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 972a1cea682f..b46a2b2575fe 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -114,6 +114,9 @@ public abstract class BroadcastQueue {
/**
* Signal from OS internals that the given process has just been actively
* attached, and is ready to begin receiving broadcasts.
+ *
+ * @return if the queue performed an action on the given process, such as
+ * dispatching a pending broadcast
*/
@GuardedBy("mService")
public abstract boolean onApplicationAttachedLocked(@NonNull ProcessRecord app);
@@ -123,7 +126,7 @@ public abstract class BroadcastQueue {
* an attempted start and attachment.
*/
@GuardedBy("mService")
- public abstract boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app);
+ public abstract void onApplicationTimeoutLocked(@NonNull ProcessRecord app);
/**
* Signal from OS internals that the given process, which had already been
@@ -131,14 +134,14 @@ public abstract class BroadcastQueue {
* not responding.
*/
@GuardedBy("mService")
- public abstract boolean onApplicationProblemLocked(@NonNull ProcessRecord app);
+ public abstract void onApplicationProblemLocked(@NonNull ProcessRecord app);
/**
* Signal from OS internals that the given process has been killed, and is
* no longer actively running.
*/
@GuardedBy("mService")
- public abstract boolean onApplicationCleanupLocked(@NonNull ProcessRecord app);
+ public abstract void onApplicationCleanupLocked(@NonNull ProcessRecord app);
/**
* Signal from OS internals that the given package (or some subset of that
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 28bd9c367fff..16711853267e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -426,16 +426,16 @@ public class BroadcastQueueImpl extends BroadcastQueue {
}
}
- public boolean onApplicationTimeoutLocked(ProcessRecord app) {
- return skipCurrentOrPendingReceiverLocked(app);
+ public void onApplicationTimeoutLocked(ProcessRecord app) {
+ skipCurrentOrPendingReceiverLocked(app);
}
- public boolean onApplicationProblemLocked(ProcessRecord app) {
- return skipCurrentOrPendingReceiverLocked(app);
+ public void onApplicationProblemLocked(ProcessRecord app) {
+ skipCurrentOrPendingReceiverLocked(app);
}
- public boolean onApplicationCleanupLocked(ProcessRecord app) {
- return skipCurrentOrPendingReceiverLocked(app);
+ public void onApplicationCleanupLocked(ProcessRecord app) {
+ skipCurrentOrPendingReceiverLocked(app);
}
public boolean sendPendingBroadcastsLocked(ProcessRecord app) {
@@ -732,9 +732,10 @@ public class BroadcastQueueImpl extends BroadcastQueue {
} catch (RemoteException ex) {
// Failed to call into the process. It's either dying or wedged. Kill it gently.
synchronized (mService) {
- Slog.w(TAG, "Can't deliver broadcast to " + app.processName
- + " (pid " + app.getPid() + "). Crashing it.");
- app.scheduleCrashLocked("can't deliver broadcast",
+ final String msg = "Failed to schedule " + intent + " to " + receiver
+ + " via " + app + ": " + ex;
+ Slog.w(TAG, msg);
+ app.scheduleCrashLocked(msg,
CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
}
throw ex;
@@ -814,7 +815,11 @@ public class BroadcastQueueImpl extends BroadcastQueue {
try {
if (DEBUG_BROADCAST_LIGHT) Slog.i(TAG_BROADCAST,
"Delivering to " + filter + " : " + r);
- if (filter.receiverList.app != null && filter.receiverList.app.isInFullBackup()) {
+ final boolean isInFullBackup = (filter.receiverList.app != null)
+ && filter.receiverList.app.isInFullBackup();
+ final boolean isKilled = (filter.receiverList.app != null)
+ && filter.receiverList.app.isKilled();
+ if (isInFullBackup || isKilled) {
// Skip delivery if full backup in progress
// If it's an ordered broadcast, we need to continue to the next receiver.
if (ordered) {
@@ -1379,8 +1384,11 @@ public class BroadcastQueueImpl extends BroadcastQueue {
processCurBroadcastLocked(r, app);
return;
} catch (RemoteException e) {
- Slog.w(TAG, "Exception when sending broadcast to "
- + r.curComponent, e);
+ final String msg = "Failed to schedule " + r.intent + " to " + info
+ + " via " + app + ": " + e;
+ Slog.w(TAG, msg);
+ app.scheduleCrashLocked(msg,
+ CannotDeliverBroadcastException.TYPE_ID, /* extras=*/ null);
} catch (RuntimeException e) {
Slog.wtf(TAG, "Failed sending broadcast to "
+ r.curComponent + " with " + r.intent, e);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index a36a9f64bded..7c236ffe7ba1 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -290,7 +290,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
// If app isn't running, and there's nothing in the queue, clean up
- if (queue.isEmpty() && !queue.isProcessWarm()) {
+ if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) {
removeProcessQueue(queue.processName, queue.uid);
}
}
@@ -420,18 +420,17 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
@Override
- public boolean onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
- return onApplicationCleanupLocked(app);
+ public void onApplicationTimeoutLocked(@NonNull ProcessRecord app) {
+ onApplicationCleanupLocked(app);
}
@Override
- public boolean onApplicationProblemLocked(@NonNull ProcessRecord app) {
- return onApplicationCleanupLocked(app);
+ public void onApplicationProblemLocked(@NonNull ProcessRecord app) {
+ onApplicationCleanupLocked(app);
}
@Override
- public boolean onApplicationCleanupLocked(@NonNull ProcessRecord app) {
- boolean didSomething = false;
+ public void onApplicationCleanupLocked(@NonNull ProcessRecord app) {
if ((mRunningColdStart != null) && (mRunningColdStart.app == app)) {
// We've been waiting for this app to cold start, and it had
// trouble; clear the slot and fail delivery below
@@ -439,7 +438,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// We might be willing to kick off another cold start
enqueueUpdateRunningList();
- didSomething = true;
}
final BroadcastProcessQueue queue = getProcessQueue(app);
@@ -449,16 +447,19 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// If queue was running a broadcast, fail it
if (queue.isActive()) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
- didSomething = true;
}
+ // Skip any pending registered receivers, since the old process
+ // would never be around to receive them
+ queue.removeMatchingBroadcasts((r, i) -> {
+ return (r.receivers.get(i) instanceof BroadcastFilter);
+ }, mBroadcastConsumerSkip);
+
// If queue has nothing else pending, consider cleaning it
if (queue.isEmpty()) {
updateRunnableList(queue);
}
}
-
- return didSomething;
}
@Override
@@ -515,6 +516,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
+ // Ignore registered receivers from a previous PID
+ if (receiver instanceof BroadcastFilter) {
+ mRunningColdStart = null;
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ return;
+ }
+
final ApplicationInfo info = ((ResolveInfo) receiver).activityInfo.applicationInfo;
final ComponentName component = ((ResolveInfo) receiver).activityInfo.getComponentName();
@@ -536,6 +544,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
} else {
mRunningColdStart = null;
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+ return;
}
}
@@ -563,7 +572,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return;
}
- // Consider additional cases where we'd want fo finish immediately
+ // Consider additional cases where we'd want to finish immediately
if (app.isInFullBackup()) {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
return;
@@ -578,7 +587,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
return;
}
- if (!r.timeoutExempt) {
+ // Ignore registered receivers from a previous PID
+ if ((receiver instanceof BroadcastFilter)
+ && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
+ finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+ return;
+ }
+
+ if (mService.mProcessesReady && !r.timeoutExempt) {
final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
mLocalHandler.sendMessageDelayed(
Message.obtain(mLocalHandler, MSG_DELIVERY_TIMEOUT, queue), timeout);
@@ -604,7 +620,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
}
if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
- setDeliveryState(queue, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
+ setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
final IApplicationThread thread = app.getThread();
if (thread != null) {
@@ -628,8 +644,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
app.mState.getReportedProcState());
}
} catch (RemoteException e) {
+ final String msg = "Failed to schedule " + r + " to " + receiver
+ + " via " + app + ": " + e;
+ Slog.w(TAG, msg);
+ app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
+ app.setKilled(true);
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
- app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
}
} else {
finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
@@ -652,7 +672,9 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
r.resultCode, r.resultData, r.resultExtras, false, r.initialSticky,
r.userId, app.mState.getReportedProcState());
} catch (RemoteException e) {
- app.scheduleCrashLocked(TAG, CannotDeliverBroadcastException.TYPE_ID, null);
+ final String msg = "Failed to schedule result of " + r + " via " + app + ": " + e;
+ Slog.w(TAG, msg);
+ app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
}
}
}
@@ -674,7 +696,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// receivers as skipped
if (r.ordered && r.resultAbort) {
for (int i = r.finishedCount + 1; i < r.receivers.size(); i++) {
- setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ setDeliveryState(null, null, r, i, r.receivers.get(i),
+ BroadcastRecord.DELIVERY_SKIPPED);
}
}
@@ -690,9 +713,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
final int index = queue.getActiveIndex();
final Object receiver = r.receivers.get(index);
- setDeliveryState(queue, r, index, receiver, deliveryState);
+ setDeliveryState(queue, app, r, index, receiver, deliveryState);
if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
+ r.anrCount++;
if (app != null && !app.isDebugging()) {
mService.appNotResponding(queue.app, TimeoutRecord
.forBroadcastReceiver("Broadcast of " + r.toShortString()));
@@ -704,7 +728,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
// Even if we have more broadcasts, if we've made reasonable progress
// and someone else is waiting, retire ourselves to avoid starvation
final boolean shouldRetire = (mRunnableHead != null)
- && (queue.getActiveCountSinceIdle() > mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
+ && (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
// We're on a roll; move onto the next broadcast for this process
@@ -733,17 +757,22 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* bookkeeping related to ordered broadcasts.
*/
private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
- @NonNull BroadcastRecord r, int index, @NonNull Object receiver,
- @DeliveryState int newDeliveryState) {
+ @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
+ @NonNull Object receiver, @DeliveryState int newDeliveryState) {
final int oldDeliveryState = getDeliveryState(r, index);
if (newDeliveryState != BroadcastRecord.DELIVERY_DELIVERED) {
- Slog.w(TAG, "Delivery state of " + r + " to " + receiver + " changed from "
+ Slog.w(TAG, "Delivery state of " + r + " to " + receiver
+ + " via " + app + " changed from "
+ deliveryStateToString(oldDeliveryState) + " to "
+ deliveryStateToString(newDeliveryState));
}
- r.setDeliveryState(index, newDeliveryState);
+ // Only apply state when we haven't already reached a terminal state;
+ // this is how we ignore racing timeout messages
+ if (!isDeliveryStateTerminal(oldDeliveryState)) {
+ r.setDeliveryState(index, newDeliveryState);
+ }
// Emit any relevant tracing results when we're changing the delivery
// state as part of running from a queue
@@ -837,7 +866,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
* of it matching a predicate.
*/
private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
- setDeliveryState(null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+ setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
};
private boolean skipMatchingBroadcasts(
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index ae7f2a5d8565..33f74f3bd779 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -128,6 +128,9 @@ final class BroadcastRecord extends Binder {
@Nullable
final BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver;
+ String cachedToString;
+ String cachedToShortString;
+
static final int IDLE = 0;
static final int APP_RECEIVE = 1;
static final int CALL_IN_RECEIVE = 2;
@@ -700,21 +703,27 @@ final class BroadcastRecord extends Binder {
@Override
public String toString() {
- String label = intent.getAction();
- if (label == null) {
- label = intent.toString();
+ if (cachedToString == null) {
+ String label = intent.getAction();
+ if (label == null) {
+ label = intent.toString();
+ }
+ cachedToString = "BroadcastRecord{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " u" + userId + " " + label + "}";
}
- return "BroadcastRecord{"
- + Integer.toHexString(System.identityHashCode(this))
- + " u" + userId + " " + label + "}";
+ return cachedToString;
}
public String toShortString() {
- String label = intent.getAction();
- if (label == null) {
- label = intent.toString();
+ if (cachedToShortString == null) {
+ String label = intent.getAction();
+ if (label == null) {
+ label = intent.toString();
+ }
+ cachedToShortString = label + "/u" + userId;
}
- return label + "/u" + userId;
+ return cachedToShortString;
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 992d41637f3f..d7b38482a4ed 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -27,6 +27,7 @@ import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY;
+import static android.os.Process.getAdvertisedMem;
import static android.os.Process.getFreeMemory;
import static android.os.Process.getTotalMemory;
import static android.os.Process.killProcessQuiet;
@@ -1532,6 +1533,7 @@ public final class ProcessList {
void getMemoryInfo(ActivityManager.MemoryInfo outInfo) {
final long homeAppMem = getMemLevel(HOME_APP_ADJ);
final long cachedAppMem = getMemLevel(CACHED_APP_MIN_ADJ);
+ outInfo.advertisedMem = getAdvertisedMem();
outInfo.availMem = getFreeMemory();
outInfo.totalMem = getTotalMemory();
outInfo.threshold = homeAppMem;
diff --git a/services/core/java/com/android/server/am/ProcessProfileRecord.java b/services/core/java/com/android/server/am/ProcessProfileRecord.java
index 7369e8fadb09..4c15308a574e 100644
--- a/services/core/java/com/android/server/am/ProcessProfileRecord.java
+++ b/services/core/java/com/android/server/am/ProcessProfileRecord.java
@@ -17,7 +17,6 @@
package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
-import static android.app.ActivityManager.processStateAmToProto;
import android.annotation.IntDef;
import android.app.IApplicationThread;
@@ -318,12 +317,6 @@ final class ProcessProfileRecord {
origBase.setState(ProcessStats.STATE_NOTHING,
tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
pkgList.getPackageListLocked());
- pkgList.forEachPackage((pkgName, holder) ->
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
- mApp.uid, mApp.processName, pkgName,
- processStateAmToProto(ProcessStats.STATE_NOTHING),
- holder.appVersion)
- );
}
origBase.makeInactive();
}
@@ -362,12 +355,6 @@ final class ProcessProfileRecord {
origBase.setState(ProcessStats.STATE_NOTHING,
tracker.getMemFactorLocked(), SystemClock.uptimeMillis(),
pkgList.getPackageListLocked());
- pkgList.forEachPackage((pkgName, holder) ->
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
- mApp.uid, mApp.processName, pkgName,
- processStateAmToProto(ProcessStats.STATE_NOTHING),
- holder.appVersion)
- );
}
origBase.makeInactive();
setBaseProcessTracker(null);
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 07b6fcdcb0ca..482e6a792a90 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -54,7 +54,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.procstats.ProcessState;
import com.android.internal.app.procstats.ProcessStats;
import com.android.internal.os.Zygote;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.wm.WindowProcessController;
import com.android.server.wm.WindowProcessListener;
@@ -1212,12 +1211,6 @@ class ProcessRecord implements WindowProcessListener {
long now = SystemClock.uptimeMillis();
baseProcessTracker.setState(ProcessStats.STATE_NOTHING,
tracker.getMemFactorLocked(), now, mPkgList.getPackageListLocked());
- mPkgList.forEachPackage((pkgName, holder) ->
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
- uid, processName, pkgName,
- ActivityManager.processStateAmToProto(ProcessStats.STATE_NOTHING),
- holder.appVersion)
- );
if (numOfPkgs != 1) {
mPkgList.forEachPackageProcessStats(holder -> {
if (holder.state != null && holder.state != baseProcessTracker) {
diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java
index ef137787905f..d2ef479ed524 100644
--- a/services/core/java/com/android/server/am/ProcessStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessStateRecord.java
@@ -35,7 +35,6 @@ import android.util.TimeUtils;
import com.android.internal.annotations.CompositeRWLock;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.FrameworkStatsLog;
import com.android.server.am.PlatformCompatCache.CachedCompatChangeId;
import java.io.PrintWriter;
@@ -599,12 +598,6 @@ final class ProcessStateRecord {
@GuardedBy({"mService", "mProcLock"})
void setReportedProcState(int repProcState) {
mRepProcState = repProcState;
- mApp.getPkgList().forEachPackage((pkgName, holder) ->
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
- mApp.uid, mApp.processName, pkgName,
- ActivityManager.processStateAmToProto(mRepProcState),
- holder.appVersion)
- );
mApp.getWindowProcessController().setReportedProcState(repProcState);
}
@@ -620,12 +613,6 @@ final class ProcessStateRecord {
mRepProcState = newState;
setCurProcState(newState);
setCurRawProcState(newState);
- mApp.getPkgList().forEachPackage((pkgName, holder) ->
- FrameworkStatsLog.write(FrameworkStatsLog.PROCESS_STATE_CHANGED,
- mApp.uid, mApp.processName, pkgName,
- ActivityManager.processStateAmToProto(mRepProcState),
- holder.appVersion)
- );
}
}
}
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 072d17f21829..bc650ad2bd76 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -105,6 +105,7 @@ import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.PackageTagsList;
import android.os.Process;
@@ -374,10 +375,17 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
public AppOpsUidStateTracker getUidStateTracker() {
if (mUidStateTracker == null) {
mUidStateTracker = new AppOpsUidStateTrackerImpl(
- LocalServices.getService(ActivityManagerInternal.class), mHandler,
+ LocalServices.getService(ActivityManagerInternal.class),
+ mHandler,
+ r -> {
+ synchronized (AppOpsService.this) {
+ r.run();
+ }
+ },
Clock.SYSTEM_CLOCK, mConstants);
- mUidStateTracker.addUidStateChangedCallback(mHandler, this::onUidStateChanged);
+ mUidStateTracker.addUidStateChangedCallback(new HandlerExecutor(mHandler),
+ this::onUidStateChanged);
}
return mUidStateTracker;
}
@@ -4809,6 +4817,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
pw.println(" Only output the watcher sections.");
pw.println(" --history");
pw.println(" Only output history.");
+ pw.println(" --uid-state-changes");
+ pw.println(" Include logs about uid state changes.");
}
private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterAttributionTag,
@@ -4946,6 +4956,7 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
// TODO ntmyren: Remove the dumpHistory and dumpFilter
boolean dumpHistory = false;
boolean includeDiscreteOps = false;
+ boolean dumpUidStateChangeLogs = false;
int nDiscreteOps = 10;
@HistoricalOpsRequestFilter int dumpFilter = 0;
boolean dumpAll = false;
@@ -5028,6 +5039,8 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
} else if (arg.length() > 0 && arg.charAt(0) == '-') {
pw.println("Unknown option: " + arg);
return;
+ } else if ("--uid-state-changes".equals(arg)) {
+ dumpUidStateChangeLogs = true;
} else {
pw.println("Unknown command: " + arg);
return;
@@ -5363,6 +5376,12 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
pw.println(" AppOps policy not set.");
}
}
+
+ if (dumpAll || dumpUidStateChangeLogs) {
+ pw.println();
+ pw.println("Uid State Changes Event Log:");
+ getUidStateTracker().dumpEvents(pw);
+ }
}
// Must not hold the appops lock
@@ -5375,14 +5394,6 @@ public class AppOpsService extends IAppOpsService.Stub implements PersistenceSch
mHistoricalRegistry.dumpDiscreteData(pw, dumpUid, dumpPackage, dumpAttributionTag,
dumpFilter, dumpOp, sdf, date, " ", nDiscreteOps);
}
-
- if (dumpAll) {
- pw.println();
- pw.println("Uid State Changes Event Log:");
- if (mUidStateTracker != null) {
- mUidStateTracker.dumpEvents(pw);
- }
- }
}
@Override
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
index 3da121bc73fa..742bf4b6ebc7 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTracker.java
@@ -30,10 +30,11 @@ import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_PERSISTENT;
import static android.app.AppOpsManager.UID_STATE_TOP;
-import android.os.Handler;
+import android.annotation.CallbackExecutor;
import android.util.SparseArray;
import java.io.PrintWriter;
+import java.util.concurrent.Executor;
interface AppOpsUidStateTracker {
@@ -95,7 +96,7 @@ interface AppOpsUidStateTracker {
/**
* Listen to changes in {@link android.app.AppOpsManager.UidState}
*/
- void addUidStateChangedCallback(Handler handler,
+ void addUidStateChangedCallback(@CallbackExecutor Executor executor,
UidStateChangedCallback callback);
/**
diff --git a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
index ca5bfb36723c..3c281d13c769 100644
--- a/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
+++ b/services/core/java/com/android/server/appop/AppOpsUidStateTrackerImpl.java
@@ -22,11 +22,11 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHO
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManager.ProcessCapability;
+import static android.app.AppOpsManager.MIN_PRIORITY_UID_STATE;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_IGNORED;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_RECORD_AUDIO;
-import static android.app.AppOpsManager.UID_STATE_CACHED;
import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
import static android.app.AppOpsManager.UID_STATE_MAX_LAST_NON_RESTRICTED;
import static android.app.AppOpsManager.UID_STATE_TOP;
@@ -44,17 +44,18 @@ import android.util.SparseIntArray;
import android.util.SparseLongArray;
import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.Clock;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.PrintWriter;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
private static final String LOG_TAG = AppOpsUidStateTrackerImpl.class.getSimpleName();
- private final Handler mHandler;
+ private final DelayableExecutor mExecutor;
private final Clock mClock;
private ActivityManagerInternal mActivityManagerInternal;
private AppOpsService.Constants mConstants;
@@ -68,18 +69,46 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
private SparseLongArray mPendingCommitTime = new SparseLongArray();
private SparseBooleanArray mPendingGone = new SparseBooleanArray();
- private ArrayMap<UidStateChangedCallback, Handler> mUidStateChangedCallbacks = new ArrayMap<>();
+ private ArrayMap<UidStateChangedCallback, Executor>
+ mUidStateChangedCallbacks = new ArrayMap<>();
private final EventLog mEventLog;
+ @VisibleForTesting
+ interface DelayableExecutor extends Executor {
+
+ void execute(Runnable runnable);
+
+ void executeDelayed(Runnable runnable, long delay);
+ }
+
AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
- Handler handler, Clock clock, AppOpsService.Constants constants) {
+ Handler handler, Executor lockingExecutor, Clock clock,
+ AppOpsService.Constants constants) {
+
+ this(activityManagerInternal, new DelayableExecutor() {
+ @Override
+ public void execute(Runnable runnable) {
+ handler.post(() -> lockingExecutor.execute(runnable));
+ }
+
+ @Override
+ public void executeDelayed(Runnable runnable, long delay) {
+ handler.postDelayed(() -> lockingExecutor.execute(runnable), delay);
+ }
+ }, clock, constants, handler.getLooper().getThread());
+ }
+
+ @VisibleForTesting
+ AppOpsUidStateTrackerImpl(ActivityManagerInternal activityManagerInternal,
+ DelayableExecutor executor, Clock clock, AppOpsService.Constants constants,
+ Thread executorThread) {
mActivityManagerInternal = activityManagerInternal;
- mHandler = handler;
+ mExecutor = executor;
mClock = clock;
mConstants = constants;
- mEventLog = new EventLog(handler);
+ mEventLog = new EventLog(executor, executorThread);
}
@Override
@@ -89,7 +118,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
private int getUidStateLocked(int uid) {
updateUidPendingStateIfNeeded(uid);
- return mUidStates.get(uid, UID_STATE_CACHED);
+ return mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
}
@Override
@@ -157,11 +186,12 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
}
@Override
- public void addUidStateChangedCallback(Handler handler, UidStateChangedCallback callback) {
+ public void addUidStateChangedCallback(Executor executor, UidStateChangedCallback callback) {
if (mUidStateChangedCallbacks.containsKey(callback)) {
throw new IllegalStateException("Callback is already registered.");
}
- mUidStateChangedCallbacks.put(callback, handler);
+
+ mUidStateChangedCallbacks.put(callback, executor);
}
@Override
@@ -191,8 +221,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
int prevUidState = mUidStates.get(uid, AppOpsManager.MIN_PRIORITY_UID_STATE);
int prevCapability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
+ int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE);
+ int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE);
long pendingStateCommitTime = mPendingCommitTime.get(uid, 0);
- if (uidState != prevUidState || capability != prevCapability) {
+ if ((pendingStateCommitTime == 0
+ && (uidState != prevUidState || capability != prevCapability))
+ || (pendingStateCommitTime != 0
+ && (uidState != pendingUidState || capability != pendingCapability))) {
mPendingUidStates.put(uid, uidState);
mPendingCapability.put(uid, capability);
@@ -227,7 +262,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
final long commitTime = mClock.elapsedRealtime() + settleTime;
mPendingCommitTime.put(uid, commitTime);
- mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
+ mExecutor.executeDelayed(PooledLambda.obtainRunnable(
AppOpsUidStateTrackerImpl::updateUidPendingStateIfNeeded, this,
uid), settleTime + 1);
}
@@ -236,7 +271,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
@Override
public void dumpUidState(PrintWriter pw, int uid, long nowElapsed) {
- int state = mUidStates.get(uid, UID_STATE_CACHED);
+ int state = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
// if no pendingState set to state to suppress output
int pendingState = mPendingUidStates.get(uid, state);
pw.print(" state=");
@@ -294,11 +329,11 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
}
private void commitUidPendingState(int uid) {
- int pendingUidState = mPendingUidStates.get(uid, UID_STATE_CACHED);
+ int pendingUidState = mPendingUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int pendingCapability = mPendingCapability.get(uid, PROCESS_CAPABILITY_NONE);
boolean pendingVisibleAppWidget = mPendingVisibleAppWidget.get(uid, false);
- int uidState = mUidStates.get(uid, UID_STATE_CACHED);
+ int uidState = mUidStates.get(uid, MIN_PRIORITY_UID_STATE);
int capability = mCapability.get(uid, PROCESS_CAPABILITY_NONE);
boolean visibleAppWidget = mVisibleAppWidget.get(uid, false);
@@ -318,10 +353,11 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
for (int i = 0; i < mUidStateChangedCallbacks.size(); i++) {
UidStateChangedCallback cb = mUidStateChangedCallbacks.keyAt(i);
- Handler h = mUidStateChangedCallbacks.valueAt(i);
+ Executor executor = mUidStateChangedCallbacks.valueAt(i);
- h.sendMessage(PooledLambda.obtainMessage(UidStateChangedCallback::onUidStateChanged,
- cb, uid, pendingUidState, foregroundChange));
+ executor.execute(PooledLambda.obtainRunnable(
+ UidStateChangedCallback::onUidStateChanged, cb, uid, pendingUidState,
+ foregroundChange));
}
}
@@ -361,7 +397,8 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
// Memory usage: 24 * size bytes
private static final int EVAL_FOREGROUND_MODE_MAX_SIZE = 200;
- private final Handler mHandler;
+ private final DelayableExecutor mExecutor;
+ private final Thread mExecutorThread;
private int[][] mUpdateUidProcStateLog = new int[UPDATE_UID_PROC_STATE_LOG_MAX_SIZE][3];
private long[] mUpdateUidProcStateLogTimestamps =
@@ -379,15 +416,16 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
private int mEvalForegroundModeLogSize = 0;
private int mEvalForegroundModeLogHead = 0;
- EventLog(Handler handler) {
- mHandler = handler;
+ EventLog(DelayableExecutor executor, Thread executorThread) {
+ mExecutor = executor;
+ mExecutorThread = executorThread;
}
void logUpdateUidProcState(int uid, int procState, int capability) {
if (UPDATE_UID_PROC_STATE_LOG_MAX_SIZE == 0) {
return;
}
- mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logUpdateUidProcStateAsync,
+ mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logUpdateUidProcStateAsync,
this, System.currentTimeMillis(), uid, procState, capability));
}
@@ -411,7 +449,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
if (COMMIT_UID_STATE_LOG_MAX_SIZE == 0) {
return;
}
- mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logCommitUidStateAsync,
+ mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logCommitUidStateAsync,
this, System.currentTimeMillis(), uid, uidState, capability, visible));
}
@@ -437,7 +475,7 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
if (EVAL_FOREGROUND_MODE_MAX_SIZE == 0) {
return;
}
- mHandler.sendMessage(PooledLambda.obtainMessage(EventLog::logEvalForegroundModeAsync,
+ mExecutor.execute(PooledLambda.obtainRunnable(EventLog::logEvalForegroundModeAsync,
this, System.currentTimeMillis(), uid, uidState, capability, code, result));
}
@@ -461,22 +499,6 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
}
void dumpEvents(PrintWriter pw) {
- if (Thread.currentThread() != mHandler.getLooper().getThread()) {
- // All operations are done on the handler's thread
- CountDownLatch latch = new CountDownLatch(1);
- mHandler.post(() -> {
- dumpEvents(pw);
- latch.countDown();
- });
-
- try {
- latch.await();
- } catch (InterruptedException e) {
- throw new RuntimeException(e);
- }
- return;
- }
-
int updateIdx = 0;
int commitIdx = 0;
int evalIdx = 0;
@@ -531,13 +553,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
pw.print(" UPDATE_UID_PROC_STATE");
pw.print(" uid=");
- pw.print(uid);
+ pw.print(String.format("%-8d", uid));
pw.print(" procState=");
pw.print(String.format("%-30s", ActivityManager.procStateToString(procState)));
pw.print(" capability=");
- pw.print(ActivityManager.getCapabilitiesSummary(capability));
+ pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
pw.println();
}
@@ -554,13 +576,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
pw.print(" COMMIT_UID_STATE ");
pw.print(" uid=");
- pw.print(uid);
+ pw.print(String.format("%-8d", uid));
pw.print(" uidState=");
pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
pw.print(" capability=");
- pw.print(ActivityManager.getCapabilitiesSummary(capability));
+ pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
pw.print(" visibleAppWidget=");
pw.print(visibleAppWidget);
@@ -581,13 +603,13 @@ class AppOpsUidStateTrackerImpl implements AppOpsUidStateTracker {
pw.print(" EVAL_FOREGROUND_MODE ");
pw.print(" uid=");
- pw.print(uid);
+ pw.print(String.format("%-8d", uid));
pw.print(" uidState=");
pw.print(String.format("%-30s", AppOpsManager.uidStateToString(uidState)));
pw.print(" capability=");
- pw.print(ActivityManager.getCapabilitiesSummary(capability));
+ pw.print(ActivityManager.getCapabilitiesSummary(capability) + " ");
pw.print(" code=");
pw.print(String.format("%-20s", AppOpsManager.opToName(code)));
diff --git a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
index d74c7025ae33..7c9a48431a24 100644
--- a/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
+++ b/services/core/java/com/android/server/display/AmbientBrightnessStatsTracker.java
@@ -27,6 +27,7 @@ import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -287,6 +288,14 @@ public class AmbientBrightnessStatsTracker {
localDate)) {
return lastBrightnessStats;
} else {
+ // It is a new day, and we have available data, so log data. The daily boundary
+ // might not be right if the user changes timezones but that is fine, since it
+ // won't be that frequent.
+ if (lastBrightnessStats != null) {
+ FrameworkStatsLog.write(FrameworkStatsLog.AMBIENT_BRIGHTNESS_STATS_REPORTED,
+ lastBrightnessStats.getStats(),
+ lastBrightnessStats.getBucketBoundaries());
+ }
AmbientBrightnessDayStats dayStats = new AmbientBrightnessDayStats(localDate,
BUCKET_BOUNDARIES_FOR_NEW_STATS);
if (userStats.size() == MAX_DAYS_TO_TRACK) {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 2cde526c6dbf..e851f03c92ef 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -80,7 +80,6 @@ import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
import android.hardware.display.WifiDisplayStatus;
import android.hardware.graphics.common.DisplayDecorationSupport;
-import android.hardware.input.InputManagerInternal;
import android.media.projection.IMediaProjection;
import android.media.projection.IMediaProjectionManager;
import android.net.Uri;
@@ -134,6 +133,7 @@ import com.android.server.UiThread;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayDeviceConfig.SensorData;
import com.android.server.display.utils.SensorUtils;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.SurfaceAnimationThread;
import com.android.server.wm.WindowManagerInternal;
diff --git a/services/core/java/com/android/server/display/TEST_MAPPING b/services/core/java/com/android/server/display/TEST_MAPPING
index 66ec5c4b1525..57c2e01297e6 100644
--- a/services/core/java/com/android/server/display/TEST_MAPPING
+++ b/services/core/java/com/android/server/display/TEST_MAPPING
@@ -7,6 +7,15 @@
{"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
{"exclude-annotation": "androidx.test.filters.FlakyTest"}
]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.display"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
+ ]
}
]
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index fee1f5c2a559..e1b18f2226b8 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -38,7 +38,6 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.database.ContentObserver;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.input.InputManagerInternal;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -66,6 +65,7 @@ import com.android.internal.util.DumpUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.ActivityInterceptorCallback;
import com.android.server.wm.ActivityTaskManagerInternal;
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index e03a46c7850e..3ee35036e9fd 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -266,10 +266,6 @@ public class HdmiControlService extends SystemService {
// Make sure HdmiCecConfig is instantiated and the XMLs are read.
private HdmiCecConfig mHdmiCecConfig;
- // Last return value of getPhysicalAddress(). Only updated on calls of getPhysicalAddress().
- // Does not represent the current physical address at all times. Not to be used as a cache.
- private int mPhysicalAddress = Constants.INVALID_PHYSICAL_ADDRESS;
-
/**
* Interface to report send result.
*/
@@ -2082,15 +2078,9 @@ public class HdmiControlService extends SystemService {
@Override
public int getPhysicalAddress() {
initBinderCall();
- runOnServiceThread(new Runnable() {
- @Override
- public void run() {
- synchronized (mLock) {
- mPhysicalAddress = mHdmiCecNetwork.getPhysicalAddress();
- }
- }
- });
- return mPhysicalAddress;
+ synchronized (mLock) {
+ return mHdmiCecNetwork.getPhysicalAddress();
+ }
}
@Override
diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index fc6bc555e823..36099b0261c2 100644
--- a/core/java/android/hardware/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.input;
+package com.android.server.input;
import android.annotation.NonNull;
import android.graphics.PointF;
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 05c4f77d5c81..71feb952cb00 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -55,8 +55,6 @@ import android.hardware.input.IInputSensorEventListener;
import android.hardware.input.ITabletModeChangedListener;
import android.hardware.input.InputDeviceIdentifier;
import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
-import android.hardware.input.InputManagerInternal.LidSwitchCallback;
import android.hardware.input.InputSensorInfo;
import android.hardware.input.KeyboardLayout;
import android.hardware.input.TouchCalibration;
@@ -123,6 +121,7 @@ import com.android.internal.util.XmlUtils;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
import com.android.server.Watchdog;
+import com.android.server.input.InputManagerInternal.LidSwitchCallback;
import com.android.server.policy.WindowManagerPolicy;
import libcore.io.IoUtils;
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index f89b6aedf1f5..a4830beecd04 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -21,7 +21,6 @@ import static android.view.InputDevice.SOURCE_STYLUS;
import android.annotation.AnyThread;
import android.annotation.Nullable;
import android.annotation.UiThread;
-import android.hardware.input.InputManagerInternal;
import android.os.IBinder;
import android.os.Looper;
import android.util.Slog;
@@ -35,6 +34,7 @@ import android.view.MotionEvent;
import android.view.SurfaceControl;
import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
import com.android.server.wm.WindowManagerInternal;
import java.util.ArrayList;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index ed17b9ca3a47..457154633977 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -92,7 +92,6 @@ import android.database.ContentObserver;
import android.graphics.Matrix;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.InputManager;
-import android.hardware.input.InputManagerInternal;
import android.inputmethodservice.InputMethodService;
import android.media.AudioManagerInternal;
import android.net.Uri;
@@ -188,6 +187,7 @@ import com.android.server.LocalServices;
import com.android.server.ServiceThread;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
+import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index a6a3db11b729..e653f0466863 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -1616,6 +1616,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
updateEnabled();
restartLocationRequest();
}
+
+ // Re-register network callbacks to get an update of available networks right away.
+ mNetworkConnectivityHandler.unregisterNetworkCallbacks();
+ mNetworkConnectivityHandler.registerNetworkCallbacks();
}
@Override
@@ -1722,7 +1726,8 @@ public class GnssLocationProvider extends AbstractLocationProvider implements
String setId = null;
int subId = SubscriptionManager.getDefaultDataSubscriptionId();
- if (mNIHandler.getInEmergency() && mNetworkConnectivityHandler.getActiveSubId() >= 0) {
+ if (mGnssConfiguration.isActiveSimEmergencySuplEnabled() && mNIHandler.getInEmergency()
+ && mNetworkConnectivityHandler.getActiveSubId() >= 0) {
subId = mNetworkConnectivityHandler.getActiveSubId();
}
if (SubscriptionManager.isValidSubscriptionId(subId)) {
diff --git a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
index dc1f4ddb19b9..02bdfd5bcb56 100644
--- a/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
+++ b/services/core/java/com/android/server/location/gnss/GnssNetworkConnectivityHandler.java
@@ -301,6 +301,10 @@ class GnssNetworkConnectivityHandler {
mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback, mHandler);
}
+ void unregisterNetworkCallbacks() {
+ mConnMgr.unregisterNetworkCallback(mNetworkConnectivityCallback);
+ }
+
/**
* @return {@code true} if there is a data network available for outgoing connections,
* {@code false} otherwise.
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index fdf7b350b813..54ed54ef771e 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -621,10 +621,9 @@ class MediaRouter2ServiceImpl {
}
}
- //TODO(b/136703681): Review this is handling multi-user properly.
- void switchUser() {
+ // TODO(b/136703681): Review this is handling multi-user properly.
+ void switchUser(int userId) {
synchronized (mLock) {
- int userId = ActivityManager.getCurrentUser();
if (mCurrentUserId != userId) {
final int oldUserId = mCurrentUserId;
mCurrentUserId = userId; // do this first
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 4f0da7952867..d7d0b42aa0a7 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -17,7 +17,9 @@
package com.android.server.media;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.app.ActivityManager;
+import android.app.UserSwitchObserver;
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
@@ -146,18 +148,27 @@ public final class MediaRouterService extends IMediaRouterService.Stub
context.registerReceiverAsUser(mReceiver, UserHandle.ALL, intentFilter, null, null);
}
- public void systemRunning() {
- IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mContext.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
- switchUser();
- }
- }
- }, filter);
-
- switchUser();
+ /**
+ * Initializes the MediaRouter service.
+ *
+ * @throws RemoteException If an error occurs while registering the {@link UserSwitchObserver}.
+ */
+ @RequiresPermission(
+ anyOf = {
+ "android.permission.INTERACT_ACROSS_USERS",
+ "android.permission.INTERACT_ACROSS_USERS_FULL"
+ })
+ public void systemRunning() throws RemoteException {
+ ActivityManager.getService()
+ .registerUserSwitchObserver(
+ new UserSwitchObserver() {
+ @Override
+ public void onUserSwitchComplete(int newUserId) {
+ switchUser(newUserId);
+ }
+ },
+ TAG);
+ switchUser(ActivityManager.getCurrentUser());
}
@Override
@@ -634,9 +645,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub
}
}
- void switchUser() {
+ void switchUser(int userId) {
synchronized (mLock) {
- int userId = ActivityManager.getCurrentUser();
if (mCurrentUserId != userId) {
final int oldUserId = mCurrentUserId;
mCurrentUserId = userId; // do this first
@@ -653,7 +663,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub
}
}
}
- mService2.switchUser();
+ mService2.switchUser(userId);
}
void clientDied(ClientRecord clientRecord) {
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 2b8a196ba528..cd074c0d4a5d 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -385,6 +385,11 @@ final class DexOptHelper {
} else if (snapshot.isInstantApp(options.getPackageName(), UserHandle.getCallingUserId())) {
return false;
}
+ var pkg = snapshot.getPackage(options.getPackageName());
+ if (pkg != null && pkg.isApex()) {
+ // skip APEX
+ return true;
+ }
if (options.isDexoptOnlySecondaryDex()) {
return mPm.getDexManager().dexoptSecondaryDex(options);
@@ -427,6 +432,10 @@ final class DexOptHelper {
// Package could not be found. Report failure.
return PackageDexOptimizer.DEX_OPT_FAILED;
}
+ if (p.isApex()) {
+ // APEX needs no dexopt
+ return PackageDexOptimizer.DEX_OPT_SKIPPED;
+ }
mPm.getPackageUsage().maybeWriteAsync(mPm.mSettings.getPackagesLocked());
mPm.mCompilerStats.maybeWriteAsync();
}
@@ -498,6 +507,9 @@ final class DexOptHelper {
if (packageState == null || pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
+ if (pkg.isApex()) {
+ throw new IllegalArgumentException("Can't dexopt APEX package: " + packageName);
+ }
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
index 404285ca5b4d..e5f7f7166186 100644
--- a/services/core/java/com/android/server/pm/InstallSource.java
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -131,7 +131,8 @@ public final class InstallSource {
@Nullable PackageSignatures initiatingPackageSignatures) {
if (initiatingPackageName == null && originatingPackageName == null
&& installerPackageName == null && initiatingPackageSignatures == null
- && !isInitiatingPackageUninstalled) {
+ && !isInitiatingPackageUninstalled
+ && packageSource == PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED) {
return isOrphaned ? EMPTY_ORPHANED : EMPTY;
}
return new InstallSource(initiatingPackageName, originatingPackageName,
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 3fb4066c965a..178d0ea594b1 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -189,6 +189,11 @@ public class PackageDexOptimizer {
return false;
}
+ // We do not dexopt APEX packages.
+ if (pkg.isApex()) {
+ return false;
+ }
+
// We do not dexopt unused packages.
// It's possible for this to be called before app hibernation service is ready due to
// an OTA dexopt. In this case, we ignore the hibernation check here. This is fine since
diff --git a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
index 9b7d19a725d1..f8fcaff354ce 100644
--- a/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
+++ b/services/core/java/com/android/server/policy/DeviceStateProviderImpl.java
@@ -26,7 +26,6 @@ import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
-import android.hardware.input.InputManagerInternal;
import android.os.Environment;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -39,6 +38,7 @@ import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.input.InputManagerInternal;
import com.android.server.policy.devicestate.config.Conditions;
import com.android.server.policy.devicestate.config.DeviceStateConfig;
import com.android.server.policy.devicestate.config.Flags;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 02d70741c0a1..07f5bcfb6e15 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -121,7 +121,6 @@ import android.hardware.hdmi.HdmiAudioSystemClient;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiPlaybackClient;
import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
-import android.hardware.input.InputManagerInternal;
import android.media.AudioManager;
import android.media.AudioManagerInternal;
import android.media.AudioSystem;
@@ -204,6 +203,7 @@ import com.android.server.ExtconUEventObserver;
import com.android.server.GestureLauncherService;
import com.android.server.LocalServices;
import com.android.server.SystemServiceManager;
+import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.KeyCombinationManager.TwoKeysCombinationRule;
import com.android.server.policy.keyguard.KeyguardServiceDelegate;
diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java
index 9336be5555e4..69fb22c28728 100644
--- a/services/core/java/com/android/server/power/Notifier.java
+++ b/services/core/java/com/android/server/power/Notifier.java
@@ -27,7 +27,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManagerInternal;
import android.media.AudioManager;
import android.media.Ringtone;
import android.media.RingtoneManager;
@@ -62,6 +61,7 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.statusbar.StatusBarManagerInternal;
diff --git a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
index 948439da4863..2d022aed1442 100644
--- a/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
+++ b/services/core/java/com/android/server/updates/ConfigUpdateInstallReceiver.java
@@ -189,6 +189,13 @@ public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
if (!parent.exists()) {
throw new IOException("Failed to create directory " + parent.getCanonicalPath());
}
+
+ // Give executable permissions to parent folders.
+ while (!(parent.equals(updateDir))) {
+ parent.setExecutable(true, false);
+ parent = parent.getParentFile();
+ }
+
// create the temporary file
tmp = File.createTempFile("journal", "", dir);
// mark tmp -rw-r--r--
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index d8d75ed9790f..9ed77fc77258 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -1354,10 +1354,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return true;
}
- void setAppTimeTracker(AppTimeTracker att) {
- appTimeTracker = att;
- }
-
/** Update the saved state of an activity. */
void setSavedState(@Nullable Bundle savedState) {
mIcicle = savedState;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 5bddae66fed3..b473700c3074 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -139,7 +139,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.content.ReferrerIntent;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService;
@@ -1578,17 +1577,12 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (rootTask.getWindowingMode() == WINDOWING_MODE_PINNED) {
removePinnedRootTaskInSurfaceTransaction(rootTask);
} else {
- final PooledConsumer c = PooledLambda.obtainConsumer(
- ActivityTaskSupervisor::processRemoveTask, this, PooledLambda.__(Task.class));
- rootTask.forAllLeafTasks(c, true /* traverseTopToBottom */);
- c.recycle();
+ rootTask.forAllLeafTasks(task -> {
+ removeTask(task, true /* killProcess */, REMOVE_FROM_RECENTS, "remove-root-task");
+ }, true /* traverseTopToBottom */);
}
}
- private void processRemoveTask(Task task) {
- removeTask(task, true /* killProcess */, REMOVE_FROM_RECENTS, "remove-root-task");
- }
-
/**
* Removes the root task associated with the given {@param task}. If the {@param task} is the
* pinned task, then its child tasks are not explicitly removed when the root task is
@@ -2268,23 +2262,17 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
void scheduleUpdateMultiWindowMode(Task task) {
- final PooledConsumer c = PooledLambda.obtainConsumer(
- ActivityTaskSupervisor::addToMultiWindowModeChangedList, this,
- PooledLambda.__(ActivityRecord.class));
- task.forAllActivities(c);
- c.recycle();
+ task.forAllActivities(r -> {
+ if (r.attachedToProcess()) {
+ mMultiWindowModeChangedActivities.add(r);
+ }
+ });
if (!mHandler.hasMessages(REPORT_MULTI_WINDOW_MODE_CHANGED_MSG)) {
mHandler.sendEmptyMessage(REPORT_MULTI_WINDOW_MODE_CHANGED_MSG);
}
}
- private void addToMultiWindowModeChangedList(ActivityRecord r) {
- if (r.attachedToProcess()) {
- mMultiWindowModeChangedActivities.add(r);
- }
- }
-
void scheduleUpdatePictureInPictureModeIfNeeded(Task task, Task prevRootTask) {
final Task rootTask = task.getRootTask();
if ((prevRootTask == null || (prevRootTask != rootTask
@@ -2296,11 +2284,14 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
void scheduleUpdatePictureInPictureModeIfNeeded(Task task, Rect targetRootTaskBounds) {
- final PooledConsumer c = PooledLambda.obtainConsumer(
- ActivityTaskSupervisor::addToPipModeChangedList, this,
- PooledLambda.__(ActivityRecord.class));
- task.forAllActivities(c);
- c.recycle();
+ task.forAllActivities(r -> {
+ if (!r.attachedToProcess()) return;
+ mPipModeChangedActivities.add(r);
+ // If we are scheduling pip change, then remove this activity from multi-window
+ // change list as the processing of pip change will make sure multi-window changed
+ // message is processed in the right order relative to pip changed.
+ mMultiWindowModeChangedActivities.remove(r);
+ });
mPipModeChangedTargetRootTaskBounds = targetRootTaskBounds;
@@ -2309,16 +2300,6 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
}
- private void addToPipModeChangedList(ActivityRecord r) {
- if (!r.attachedToProcess()) return;
-
- mPipModeChangedActivities.add(r);
- // If we are scheduling pip change, then remove this activity from multi-window
- // change list as the processing of pip change will make sure multi-window changed
- // message is processed in the right order relative to pip changed.
- mMultiWindowModeChangedActivities.remove(r);
- }
-
void wakeUp(String reason) {
mPowerManager.wakeUp(SystemClock.uptimeMillis(), PowerManager.WAKE_REASON_APPLICATION,
"android.server.am:TURN_ON:" + reason);
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 8c5f05365837..7d9ae87517b0 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -202,8 +202,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
// target windows. But the windows still need to use sync transaction to keep the appearance
// in previous rotation, so request a no-op sync to keep the state.
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
- if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
- && mTargetWindowTokens.valueAt(i).mAction != Operation.ACTION_SEAMLESS) {
+ if (mTargetWindowTokens.valueAt(i).canDrawBeforeStartTransaction()) {
// Expect a screenshot layer will cover the non seamless windows.
continue;
}
@@ -489,7 +488,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
return false;
}
final Operation op = mTargetWindowTokens.get(w.mToken);
- if (op == null) return false;
+ if (op == null || op.canDrawBeforeStartTransaction()) return false;
if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
if (op.mDrawTransaction == null) {
if (w.isClientLocal()) {
@@ -554,5 +553,14 @@ class AsyncRotationController extends FadeAnimationController implements Consume
Operation(@Action int action) {
mAction = action;
}
+
+ /**
+ * Returns {@code true} if the corresponding window can draw its latest content before the
+ * start transaction of rotation transition is applied.
+ */
+ boolean canDrawBeforeStartTransaction() {
+ return TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST
+ && mAction != ACTION_SEAMLESS;
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index acbf1a4cd8f1..6e23ed966ddc 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -391,8 +391,7 @@ final class ContentRecorder implements WindowContainerListener {
* </p>
*/
private void handleStartRecordingFailed() {
- final boolean shouldExitTaskRecording = mContentRecordingSession != null
- && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
+ final boolean shouldExitTaskRecording = isRecordingContentTask();
clearContentRecordingSession();
if (shouldExitTaskRecording) {
// Clean up the cached session first to ensure recording doesn't re-start, since
@@ -478,9 +477,10 @@ final class ContentRecorder implements WindowContainerListener {
ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
"Recorded task is removed, so stop recording on display %d",
mDisplayContent.getDisplayId());
- Task recordedTask = mRecordedWindowContainer.asTask();
- if (recordedTask == null
- || mContentRecordingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
+
+ Task recordedTask = mRecordedWindowContainer != null
+ ? mRecordedWindowContainer.asTask() : null;
+ if (recordedTask == null || !isRecordingContentTask()) {
return;
}
recordedTask.unregisterWindowContainerListener(this);
@@ -504,4 +504,9 @@ final class ContentRecorder implements WindowContainerListener {
@VisibleForTesting interface MediaProjectionManagerWrapper {
void stopActiveProjection();
}
+
+ private boolean isRecordingContentTask() {
+ return mContentRecordingSession != null
+ && mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c6fe017b1917..669cd5d8f812 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -241,7 +241,6 @@ import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ToBooleanFunction;
import com.android.internal.util.function.TriConsumer;
-import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.inputmethod.InputMethodManagerInternal;
@@ -3075,18 +3074,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
boolean pointWithinAppWindow(int x, int y) {
final int[] targetWindowType = {-1};
- final PooledConsumer fn = PooledLambda.obtainConsumer((w, nonArg) -> {
- if (targetWindowType[0] != -1) {
- return;
- }
-
+ forAllWindows(w -> {
if (w.isOnScreen() && w.isVisible() && w.getFrame().contains(x, y)) {
targetWindowType[0] = w.mAttrs.type;
- return;
+ return true;
}
- }, PooledLambda.__(WindowState.class), mTmpRect);
- forAllWindows(fn, true /* traverseTopToBottom */);
- fn.recycle();
+ return false;
+ }, true /* traverseTopToBottom */);
return FIRST_APPLICATION_WINDOW <= targetWindowType[0]
&& targetWindowType[0] <= LAST_APPLICATION_WINDOW;
}
@@ -3112,11 +3106,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mTmpRect.setEmpty();
mTmpRect2.setEmpty();
- final PooledConsumer c = PooledLambda.obtainConsumer(
- DisplayContent::processTaskForTouchExcludeRegion, this,
- PooledLambda.__(Task.class), focusedTask, delta);
- forAllTasks(c);
- c.recycle();
+ forAllTasks(t -> { processTaskForTouchExcludeRegion(t, focusedTask, delta); });
// If we removed the focused task above, add it back and only leave its
// outside touch area in the exclusion. TapDetector is not interested in
@@ -6187,17 +6177,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/** Update and get all UIDs that are present on the display and have access to it. */
IntArray getPresentUIDs() {
mDisplayAccessUIDs.clear();
- final PooledConsumer c = PooledLambda.obtainConsumer(DisplayContent::addActivityUid,
- PooledLambda.__(ActivityRecord.class), mDisplayAccessUIDs);
- mDisplayContent.forAllActivities(c);
- c.recycle();
+ mDisplayContent.forAllActivities(r -> { mDisplayAccessUIDs.add(r.getUid()); });
return mDisplayAccessUIDs;
}
- private static void addActivityUid(ActivityRecord r, IntArray uids) {
- uids.add(r.getUid());
- }
-
@VisibleForTesting
boolean shouldDestroyContentOnRemove() {
return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index e7806060c14f..ac61cb940e49 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2167,10 +2167,7 @@ public class DisplayPolicy {
* If the decor insets changes, the display configuration may be affected. The caller should
* call {@link DisplayContent#sendNewConfiguration()} if this method returns {@code true}.
*/
- boolean updateDecorInsetsInfoIfNeeded(WindowState win) {
- if (!win.providesNonDecorInsets()) {
- return false;
- }
+ boolean updateDecorInsetsInfo() {
final DisplayFrames displayFrames = mDisplayContent.mDisplayFrames;
final int rotation = displayFrames.mRotation;
final int dw = displayFrames.mWidth;
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 7bb57d827a43..bffab7a4199d 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -62,8 +62,6 @@ import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
@@ -116,7 +114,7 @@ public class RecentsAnimationController implements DeathRecipient {
private boolean mWillFinishToHome = false;
private final Runnable mFailsafeRunnable = this::onFailsafe;
- // The recents component app token that is shown behind the visibile tasks
+ // The recents component app token that is shown behind the visible tasks
private ActivityRecord mTargetActivityRecord;
private DisplayContent mDisplayContent;
private int mTargetActivityType;
@@ -403,11 +401,11 @@ public class RecentsAnimationController implements DeathRecipient {
final Task targetRootTask = mDisplayContent.getDefaultTaskDisplayArea()
.getRootTask(WINDOWING_MODE_UNDEFINED, targetActivityType);
if (targetRootTask != null) {
- final PooledConsumer c = PooledLambda.obtainConsumer((t, outList) ->
- { if (!outList.contains(t)) outList.add(t); }, PooledLambda.__(Task.class),
- visibleTasks);
- targetRootTask.forAllLeafTasks(c, true /* traverseTopToBottom */);
- c.recycle();
+ targetRootTask.forAllLeafTasks(t -> {
+ if (!visibleTasks.contains(t)) {
+ visibleTasks.add(t);
+ }
+ }, true /* traverseTopToBottom */);
}
final int taskCount = visibleTasks.size();
@@ -456,6 +454,22 @@ public class RecentsAnimationController implements DeathRecipient {
}
}
+ /**
+ * Return whether the given window should still be considered interesting for the all-drawn
+ * state. This is only interesting for the target app, which may have child windows that are
+ * not actually visible and should not be considered interesting and waited upon.
+ */
+ protected boolean isInterestingForAllDrawn(WindowState window) {
+ if (isTargetApp(window.getActivityRecord())) {
+ if (window.getWindowType() != TYPE_BASE_APPLICATION
+ && window.getAttrs().alpha == 0f) {
+ // If there is a cihld window that is alpha 0, then ignore that window
+ return false;
+ }
+ }
+ // By default all windows are still interesting for all drawn purposes
+ return true;
+ }
/**
* Whether a task should be filtered from the recents animation. This can be true for tasks
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ba834def88c7..b2ec3f369e9e 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3102,9 +3102,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
void finishVoiceTask(IVoiceInteractionSession session) {
- forAllRootTasks(rootTask -> {
- rootTask.finishVoiceTask(session);
- });
+ final IBinder binder = session.asBinder();
+ forAllLeafTasks(t -> t.finishIfVoiceTask(binder), true /* traverseTopToBottom */);
}
/**
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 120fec0fe0e6..a753b5559e07 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -23,18 +23,16 @@ import android.app.ActivityManager.RunningTaskInfo;
import android.os.UserHandle;
import android.util.ArraySet;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
-
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
+import java.util.function.Consumer;
/**
* Class for resolving the set of running tasks in the system.
*/
-class RunningTasks {
+class RunningTasks implements Consumer<Task> {
static final int FLAG_FILTER_ONLY_VISIBLE_RECENTS = 1;
static final int FLAG_ALLOWED = 1 << 1;
@@ -61,7 +59,7 @@ class RunningTasks {
private boolean mKeepIntentExtra;
void getTasks(int maxNum, List<RunningTaskInfo> list, int flags, RecentTasks recentTasks,
- WindowContainer root, int callingUid, ArraySet<Integer> profileIds) {
+ WindowContainer<?> root, int callingUid, ArraySet<Integer> profileIds) {
// Return early if there are no tasks to fetch
if (maxNum <= 0) {
return;
@@ -79,10 +77,7 @@ class RunningTasks {
mRecentTasks = recentTasks;
mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA;
- final PooledConsumer c = PooledLambda.obtainConsumer(RunningTasks::processTask, this,
- PooledLambda.__(Task.class));
- root.forAllLeafTasks(c, false);
- c.recycle();
+ root.forAllLeafTasks(this, false /* traverseTopToBottom */);
// Take the first {@param maxNum} tasks and create running task infos for them
final Iterator<Task> iter = mTmpSortedSet.iterator();
@@ -97,7 +92,8 @@ class RunningTasks {
}
}
- private void processTask(Task task) {
+ @Override
+ public void accept(Task task) {
if (task.getTopNonFinishingActivity() == null) {
// Skip if there are no activities in the task
return;
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index b9739f03bec5..e1a1f5737170 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -291,7 +291,11 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public void finishDrawing(IWindow window,
@Nullable SurfaceControl.Transaction postDrawTransaction, int seqId) {
if (DEBUG) Slog.v(TAG_WM, "IWindow finishDrawing called for " + window);
+ if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishDrawing: " + mPackageName);
+ }
mService.finishDrawingWindow(this, window, postDrawTransaction, seqId);
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@Override
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 15088704fd9b..63c6c35a8e1e 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -196,7 +196,6 @@ import com.android.internal.app.IVoiceInteractor;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.XmlUtils;
-import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.Watchdog;
@@ -1188,11 +1187,7 @@ class Task extends TaskFragment {
if (oldParent != null) {
final Task oldParentTask = oldParent.asTask();
if (oldParentTask != null) {
- final PooledConsumer c = PooledLambda.obtainConsumer(
- Task::cleanUpActivityReferences, oldParentTask,
- PooledLambda.__(ActivityRecord.class));
- forAllActivities(c);
- c.recycle();
+ forAllActivities(oldParentTask::cleanUpActivityReferences);
}
if (oldParent.inPinnedWindowingMode()
@@ -2371,10 +2366,7 @@ class Task extends TaskFragment {
int getDescendantTaskCount() {
final int[] currentCount = {0};
- final PooledConsumer c = PooledLambda.obtainConsumer((t, count) -> { count[0]++; },
- PooledLambda.__(Task.class), currentCount);
- forAllLeafTasks(c, false /* traverseTopToBottom */);
- c.recycle();
+ forAllLeafTasks(t -> currentCount[0]++, false /* traverseTopToBottom */);
return currentCount[0];
}
@@ -2783,10 +2775,7 @@ class Task extends TaskFragment {
&& displayContent.mDividerControllerLocked.isResizing();
if (inFreeformWindowingMode()) {
boolean[] foundTop = { false };
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::getMaxVisibleBounds,
- PooledLambda.__(ActivityRecord.class), out, foundTop);
- forAllActivities(c);
- c.recycle();
+ forAllActivities(a -> { getMaxVisibleBounds(a, out, foundTop); });
if (foundTop[0]) {
return;
}
@@ -4349,14 +4338,6 @@ class Task extends TaskFragment {
}
}
-
- void setActivityWindowingMode(int windowingMode) {
- PooledConsumer c = PooledLambda.obtainConsumer(ActivityRecord::setWindowingMode,
- PooledLambda.__(ActivityRecord.class), windowingMode);
- forAllActivities(c);
- c.recycle();
- }
-
/**
* Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
* @return Whether the force hidden state changed
@@ -5209,26 +5190,19 @@ class Task extends TaskFragment {
return finishedTask;
}
- void finishVoiceTask(IVoiceInteractionSession session) {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::finishIfVoiceTask,
- PooledLambda.__(Task.class), session.asBinder());
- forAllLeafTasks(c, true /* traverseTopToBottom */);
- c.recycle();
- }
-
- private static void finishIfVoiceTask(Task tr, IBinder binder) {
- if (tr.voiceSession != null && tr.voiceSession.asBinder() == binder) {
- tr.forAllActivities((r) -> {
+ void finishIfVoiceTask(IBinder binder) {
+ if (voiceSession != null && voiceSession.asBinder() == binder) {
+ forAllActivities((r) -> {
if (r.finishing) return;
r.finishIfPossible("finish-voice", false /* oomAdj */);
- tr.mAtmService.updateOomAdj();
+ mAtmService.updateOomAdj();
});
} else {
// Check if any of the activities are using voice
final PooledPredicate f = PooledLambda.obtainPredicate(
Task::finishIfVoiceActivity, PooledLambda.__(ActivityRecord.class),
binder);
- tr.forAllActivities(f);
+ forAllActivities(f);
f.recycle();
}
}
@@ -5447,10 +5421,7 @@ class Task extends TaskFragment {
if (timeTracker != null) {
// The caller wants a time tracker associated with this task.
- final PooledConsumer c = PooledLambda.obtainConsumer(ActivityRecord::setAppTimeTracker,
- PooledLambda.__(ActivityRecord.class), timeTracker);
- tr.forAllActivities(c);
- c.recycle();
+ tr.forAllActivities(a -> { a.appTimeTracker = timeTracker; });
}
try {
@@ -5621,11 +5592,11 @@ class Task extends TaskFragment {
try {
// TODO: Why not just set this on the root task directly vs. on each tasks?
// Update override configurations of all tasks in the root task.
- final PooledConsumer c = PooledLambda.obtainConsumer(
- Task::processTaskResizeBounds, PooledLambda.__(Task.class),
- displayedBounds);
- forAllTasks(c, true /* traverseTopToBottom */);
- c.recycle();
+ forAllTasks(task -> {
+ if (task.isResizeable()) {
+ task.setBounds(displayedBounds);
+ }
+ }, true /* traverseTopToBottom */);
if (!deferResume) {
ensureVisibleActivitiesConfiguration(topRunningActivity(), preserveWindows);
@@ -5636,12 +5607,6 @@ class Task extends TaskFragment {
}
}
- private static void processTaskResizeBounds(Task task, Rect displayedBounds) {
- if (!task.isResizeable()) return;
-
- task.setBounds(displayedBounds);
- }
-
boolean willActivityBeVisible(IBinder token) {
final ActivityRecord r = ActivityRecord.forTokenLocked(token);
if (r == null) {
@@ -5727,22 +5692,15 @@ class Task extends TaskFragment {
// All activities that came from the package must be
// restarted as if there was a config change.
- PooledConsumer c = PooledLambda.obtainConsumer(Task::restartPackage,
- PooledLambda.__(ActivityRecord.class), starting, packageName);
- forAllActivities(c);
- c.recycle();
-
- return starting;
- }
-
- private static void restartPackage(
- ActivityRecord r, ActivityRecord starting, String packageName) {
- if (r.info.packageName.equals(packageName)) {
+ forAllActivities(r -> {
+ if (!r.info.packageName.equals(packageName)) return;
r.forceNewConfig = true;
if (starting != null && r == starting && r.mVisibleRequested) {
r.startFreezingScreenLocked(CONFIG_SCREEN_LAYOUT);
}
- }
+ });
+
+ return starting;
}
Task reuseOrCreateTask(ActivityInfo info, Intent intent, boolean toTop) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 2cfc563b0d6e..4f1a561b14df 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1719,7 +1719,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
ProtoLog.v(WM_DEBUG_STATES, "Executing finish of activity: %s", prev);
prev = prev.completeFinishing(false /* updateVisibility */,
"completePausedLocked");
- } else if (prev.hasProcess()) {
+ } else if (prev.attachedToProcess()) {
ProtoLog.v(WM_DEBUG_STATES, "Enqueue pending stop if needed: %s "
+ "wasStopping=%b visibleRequested=%b", prev, wasStopping,
prev.mVisibleRequested);
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 69d86b60302b..9763df6b967a 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -477,7 +477,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
mLocalInsetsSourceProviders.remove(insetsTypes[i]);
}
- mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+ // Update insets if this window is attached.
+ if (mDisplayContent != null) {
+ mDisplayContent.getInsetsStateController().updateAboveInsetsState(true);
+ }
}
/**
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index e23e2065e067..29ab56200e5b 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -309,8 +309,6 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.LatencyTracker;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.view.WindowManagerPolicyThread;
import com.android.server.AnimationThread;
import com.android.server.DisplayThread;
@@ -475,10 +473,7 @@ public class WindowManagerService extends IWindowManager.Stub
public void onVrStateChanged(boolean enabled) {
synchronized (mGlobalLock) {
mVrModeEnabled = enabled;
- final PooledConsumer c = PooledLambda.obtainConsumer(
- DisplayPolicy::onVrStateChangedLw, PooledLambda.__(), enabled);
- mRoot.forAllDisplayPolicies(c);
- c.recycle();
+ mRoot.forAllDisplayPolicies(p -> p.onVrStateChangedLw(enabled));
}
}
};
@@ -900,11 +895,8 @@ public class WindowManagerService extends IWindowManager.Stub
}
mPointerLocationEnabled = enablePointerLocation;
synchronized (mGlobalLock) {
- final PooledConsumer c = PooledLambda.obtainConsumer(
- DisplayPolicy::setPointerLocationEnabled, PooledLambda.__(),
- mPointerLocationEnabled);
- mRoot.forAllDisplayPolicies(c);
- c.recycle();
+ mRoot.forAllDisplayPolicies(
+ p -> p.setPointerLocationEnabled(mPointerLocationEnabled));
}
}
@@ -1834,8 +1826,12 @@ public class WindowManagerService extends IWindowManager.Stub
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
+ ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
- if ((win.isVisibleRequestedOrAdding() && displayContent.updateOrientation())
- || displayPolicy.updateDecorInsetsInfoIfNeeded(win)) {
+ boolean needToSendNewConfiguration =
+ win.isVisibleRequestedOrAdding() && displayContent.updateOrientation();
+ if (win.providesNonDecorInsets()) {
+ needToSendNewConfiguration |= displayPolicy.updateDecorInsetsInfo();
+ }
+ if (needToSendNewConfiguration) {
displayContent.sendNewConfiguration();
}
@@ -2304,8 +2300,8 @@ public class WindowManagerService extends IWindowManager.Stub
& WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED) != 0) {
win.mLayoutNeeded = true;
}
- if (layoutChanged) {
- configChanged = displayPolicy.updateDecorInsetsInfoIfNeeded(win);
+ if (layoutChanged && win.providesNonDecorInsets()) {
+ configChanged = displayPolicy.updateDecorInsetsInfo();
}
if (win.mActivityRecord != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
|| (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
@@ -3135,10 +3131,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void onPowerKeyDown(boolean isScreenOn) {
- final PooledConsumer c = PooledLambda.obtainConsumer(
- DisplayPolicy::onPowerKeyDown, PooledLambda.__(), isScreenOn);
- mRoot.forAllDisplayPolicies(c);
- c.recycle();
+ mRoot.forAllDisplayPolicies(p -> p.onPowerKeyDown(isScreenOn));
}
@Override
@@ -8387,10 +8380,7 @@ public class WindowManagerService extends IWindowManager.Stub
void onLockTaskStateChanged(int lockTaskState) {
// TODO: pass in displayId to determine which display the lock task state changed
synchronized (mGlobalLock) {
- final PooledConsumer c = PooledLambda.obtainConsumer(
- DisplayPolicy::onLockTaskStateChangedLw, PooledLambda.__(), lockTaskState);
- mRoot.forAllDisplayPolicies(c);
- c.recycle();
+ mRoot.forAllDisplayPolicies(p -> p.onLockTaskStateChangedLw(lockTaskState));
}
}
@@ -9282,4 +9272,46 @@ public class WindowManagerService extends IWindowManager.Stub
"Unexpected letterbox background type: " + letterboxBackgroundType);
}
}
+
+ @Override
+ public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
+ ScreenCapture.ScreenCaptureListener listener) {
+ Slog.d(TAG, "captureDisplay");
+ if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {
+ throw new SecurityException("Requires READ_FRAME_BUFFER permission");
+ }
+
+ ScreenCapture.captureLayers(getCaptureArgs(displayId, captureArgs), listener);
+ }
+
+ @VisibleForTesting
+ ScreenCapture.LayerCaptureArgs getCaptureArgs(int displayId,
+ @Nullable ScreenCapture.CaptureArgs captureArgs) {
+ final SurfaceControl displaySurfaceControl;
+ synchronized (mGlobalLock) {
+ DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent == null) {
+ throw new IllegalArgumentException("Trying to screenshot and invalid display: "
+ + displayId);
+ }
+
+ displaySurfaceControl = displayContent.getSurfaceControl();
+
+ if (captureArgs == null) {
+ captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+ .build();
+ }
+
+ if (captureArgs.mSourceCrop.isEmpty()) {
+ displayContent.getBounds(mTmpRect);
+ mTmpRect.offsetTo(0, 0);
+ } else {
+ mTmpRect.set(captureArgs.mSourceCrop);
+ }
+ }
+
+ return new ScreenCapture.LayerCaptureArgs.Builder(displaySurfaceControl, captureArgs)
+ .setSourceCrop(mTmpRect)
+ .build();
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 2e1477ddf0f1..e2f833c9fc5a 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -91,8 +91,6 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.pm.LauncherAppsService.LauncherAppsServiceInternal;
@@ -482,7 +480,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
final int hopSize = hops.size();
- ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+ final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>();
Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
while (entries.hasNext()) {
@@ -592,16 +590,10 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
} else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
- final PooledConsumer f = PooledLambda.obtainConsumer(
- ActivityRecord::ensureActivityConfiguration,
- PooledLambda.__(ActivityRecord.class), 0,
- true /* preserveWindow */);
- try {
- for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
- haveConfigChanges.valueAt(i).forAllActivities(f);
- }
- } finally {
- f.recycle();
+ for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+ haveConfigChanges.valueAt(i).forAllActivities(r -> {
+ r.ensureActivityConfiguration(0, PRESERVE_WINDOWS);
+ });
}
}
@@ -716,7 +708,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final int childWindowingMode = c.getActivityWindowingMode();
if (childWindowingMode > -1) {
- tr.setActivityWindowingMode(childWindowingMode);
+ tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); });
}
if (t != null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6074dc8e42b1..68fabc505761 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2042,9 +2042,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* it must be drawn before allDrawn can become true.
*/
boolean isInteresting() {
+ final RecentsAnimationController recentsAnimationController =
+ mWmService.getRecentsAnimationController();
return mActivityRecord != null && !mAppDied
&& (!mActivityRecord.isFreezingScreen() || !mAppFreezing)
- && mViewVisibility == View.VISIBLE;
+ && mViewVisibility == View.VISIBLE
+ && (recentsAnimationController == null
+ || recentsAnimationController.isInterestingForAllDrawn(this));
}
/**
@@ -2622,11 +2626,19 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ // Check if window provides non decor insets before clearing its provided insets.
+ final boolean windowProvidesNonDecorInsets = providesNonDecorInsets();
+
removeImmediately();
// Removing a visible window may affect the display orientation so just update it if
// needed. Also recompute configuration if it provides screen decor insets.
- if ((wasVisible && displayContent.updateOrientation())
- || displayContent.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(this)) {
+ boolean needToSendNewConfiguration = wasVisible && displayContent.updateOrientation();
+ if (windowProvidesNonDecorInsets) {
+ needToSendNewConfiguration |=
+ displayContent.getDisplayPolicy().updateDecorInsetsInfo();
+ }
+
+ if (needToSendNewConfiguration) {
displayContent.sendNewConfiguration();
}
mWmService.updateFocusedWindowLocked(isFocused()
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 78b4ce222dd3..3f380e7914d0 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -168,8 +168,9 @@ static struct {
jmethodID constructor;
jfieldID lightTypeInput;
jfieldID lightTypePlayerId;
+ jfieldID lightTypeKeyboardBacklight;
jfieldID lightCapabilityBrightness;
- jfieldID lightCapabilityRgb;
+ jfieldID lightCapabilityColorRgb;
} gLightClassInfo;
static struct {
@@ -2011,25 +2012,28 @@ static jobject nativeGetLights(JNIEnv* env, jobject nativeImplObj, jint deviceId
jint jTypeId =
env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeInput);
- jint jCapability = 0;
-
- if (lightInfo.type == InputDeviceLightType::MONO) {
- jCapability = env->GetStaticIntField(gLightClassInfo.clazz,
- gLightClassInfo.lightCapabilityBrightness);
- } else if (lightInfo.type == InputDeviceLightType::RGB ||
- lightInfo.type == InputDeviceLightType::MULTI_COLOR) {
- jCapability =
- env->GetStaticIntField(gLightClassInfo.clazz,
- gLightClassInfo.lightCapabilityBrightness) |
- env->GetStaticIntField(gLightClassInfo.clazz,
- gLightClassInfo.lightCapabilityRgb);
+ if (lightInfo.type == InputDeviceLightType::INPUT) {
+ jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeInput);
} else if (lightInfo.type == InputDeviceLightType::PLAYER_ID) {
jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
gLightClassInfo.lightTypePlayerId);
+ } else if (lightInfo.type == InputDeviceLightType::KEYBOARD_BACKLIGHT) {
+ jTypeId = env->GetStaticIntField(gLightClassInfo.clazz,
+ gLightClassInfo.lightTypeKeyboardBacklight);
} else {
ALOGW("Unknown light type %d", lightInfo.type);
continue;
}
+
+ jint jCapability = 0;
+ if (lightInfo.capabilityFlags.test(InputDeviceLightCapability::BRIGHTNESS)) {
+ jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
+ gLightClassInfo.lightCapabilityBrightness);
+ }
+ if (lightInfo.capabilityFlags.test(InputDeviceLightCapability::RGB)) {
+ jCapability |= env->GetStaticIntField(gLightClassInfo.clazz,
+ gLightClassInfo.lightCapabilityColorRgb);
+ }
ScopedLocalRef<jobject> lightObj(env,
env->NewObject(gLightClassInfo.clazz,
gLightClassInfo.constructor,
@@ -2596,10 +2600,12 @@ int register_android_server_InputManager(JNIEnv* env) {
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT", "I");
gLightClassInfo.lightTypePlayerId =
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_PLAYER_ID", "I");
+ gLightClassInfo.lightTypeKeyboardBacklight =
+ env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_KEYBOARD_BACKLIGHT", "I");
gLightClassInfo.lightCapabilityBrightness =
env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_BRIGHTNESS", "I");
- gLightClassInfo.lightCapabilityRgb =
- env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_RGB", "I");
+ gLightClassInfo.lightCapabilityColorRgb =
+ env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_CAPABILITY_COLOR_RGB", "I");
// ArrayList
FIND_CLASS(gArrayListClassInfo.clazz, "java/util/ArrayList");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
index 43432258045c..16876ac64e71 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerServiceShellCommand.java
@@ -45,13 +45,10 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
"mark-profile-owner-on-organization-owned-device";
private static final String USER_OPTION = "--user";
- private static final String NAME_OPTION = "--name";
private static final String DO_ONLY_OPTION = "--device-owner-only";
private final DevicePolicyManagerService mService;
private int mUserId = UserHandle.USER_SYSTEM;
- //TODO(b/240562946): remove mName once it is not used by setDeviceOwner
- private String mName = "";
private ComponentName mComponent;
private boolean mSetDoOnly;
@@ -133,16 +130,16 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
CMD_SET_ACTIVE_ADMIN, USER_OPTION);
pw.printf(" Sets the given component as active admin for an existing user.\n\n");
- pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s <NAME> ] [ %s ]"
- + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, NAME_OPTION, DO_ONLY_OPTION);
+ pw.printf(" %s [ %s <USER_ID> | current *EXPERIMENTAL* ] [ %s ]"
+ + "<COMPONENT>\n", CMD_SET_DEVICE_OWNER, USER_OPTION, DO_ONLY_OPTION);
pw.printf(" Sets the given component as active admin, and its package as device owner."
+ "\n\n");
- pw.printf(" %s [ %s <USER_ID> | current ] [ %s <NAME> ] <COMPONENT>\n",
- CMD_SET_PROFILE_OWNER, USER_OPTION, NAME_OPTION);
+ pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
+ CMD_SET_PROFILE_OWNER, USER_OPTION);
pw.printf(" Sets the given component as active admin and profile owner for an existing "
+ "user.\n\n");
- pw.printf(" %s [ %s <USER_ID> | current ] [ %s <NAME> ] <COMPONENT>\n",
- CMD_REMOVE_ACTIVE_ADMIN, USER_OPTION, NAME_OPTION);
+ pw.printf(" %s [ %s <USER_ID> | current ] <COMPONENT>\n",
+ CMD_REMOVE_ACTIVE_ADMIN, USER_OPTION);
pw.printf(" Disables an active admin, the admin must have declared android:testOnly in "
+ "the application in its manifest. This will also remove device and profile "
+ "owners.\n\n");
@@ -245,7 +242,7 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
}
private int runSetActiveAdmin(PrintWriter pw) {
- parseArgs(/* canHaveName= */ false);
+ parseArgs();
mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
pw.printf("Success: Active admin set to component %s\n", mComponent.flattenToShortString());
@@ -253,7 +250,7 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
}
private int runSetDeviceOwner(PrintWriter pw) {
- parseArgs(/* canHaveName= */ true);
+ parseArgs();
mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
try {
@@ -277,14 +274,14 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
}
private int runRemoveActiveAdmin(PrintWriter pw) {
- parseArgs(/* canHaveName= */ false);
+ parseArgs();
mService.forceRemoveActiveAdmin(mComponent, mUserId);
pw.printf("Success: Admin removed %s\n", mComponent);
return 0;
}
private int runSetProfileOwner(PrintWriter pw) {
- parseArgs(/* canHaveName= */ true);
+ parseArgs();
mService.setActiveAdmin(mComponent, /* refreshing= */ true, mUserId);
try {
@@ -340,13 +337,13 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
}
private int runMarkProfileOwnerOnOrganizationOwnedDevice(PrintWriter pw) {
- parseArgs(/* canHaveName= */ false);
+ parseArgs();
mService.setProfileOwnerOnOrganizationOwnedDevice(mComponent, mUserId, true);
pw.printf("Success\n");
return 0;
}
- private void parseArgs(boolean canHaveName) {
+ private void parseArgs() {
String opt;
while ((opt = getNextOption()) != null) {
if (USER_OPTION.equals(opt)) {
@@ -357,8 +354,6 @@ final class DevicePolicyManagerServiceShellCommand extends ShellCommand {
}
} else if (DO_ONLY_OPTION.equals(opt)) {
mSetDoOnly = true;
- } else if (canHaveName && NAME_OPTION.equals(opt)) {
- mName = getNextArgRequired();
} else {
throw new IllegalArgumentException("Unknown option: " + opt);
}
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 6196c49c88ce..9c9b363b948e 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -1287,8 +1287,8 @@ int IncrementalService::addBindMount(IncFsMount& ifs, StorageId storage,
bp.set_allocated_dest_path(&target);
bp.set_allocated_source_subdir(&source);
const auto metadata = bp.SerializeAsString();
- bp.release_dest_path();
- bp.release_source_subdir();
+ static_cast<void>(bp.release_dest_path());
+ static_cast<void>(bp.release_source_subdir());
mdFileName = makeBindMdName();
metadataFullPath = path::join(ifs.root, constants().mount, mdFileName);
auto node = mIncFs->makeFile(ifs.control, metadataFullPath, 0444, idFromMetadata(metadata),
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
index 525a9311a723..c8d153ad37a5 100644
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
+++ b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
@@ -23,7 +23,6 @@ import android.app.AppGlobals;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
-import android.hardware.input.InputManagerInternal;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -35,6 +34,7 @@ import android.view.selectiontoolbar.ShowInfo;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.infra.AbstractPerUserSystemService;
+import com.android.server.input.InputManagerInternal;
final class SelectionToolbarManagerServiceImpl extends
AbstractPerUserSystemService<SelectionToolbarManagerServiceImpl,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
new file mode 100644
index 000000000000..09df96f4b917
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceInjectorTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.os.UserManager;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder;
+import com.android.server.ExtendedMockitoTestCase;
+import com.android.server.am.ActivityManagerService.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+
+/**
+ * Run as {@code atest
+ * FrameworksMockingServicesTests:com.android.server.am.ActivityManagerServiceInjectorTest}
+ */
+public final class ActivityManagerServiceInjectorTest extends ExtendedMockitoTestCase {
+
+ private static final String TAG = ActivityManagerServiceInjectorTest.class.getSimpleName();
+
+ private final Display mDefaultDisplay = validDisplay(DEFAULT_DISPLAY);
+
+ @Mock private Context mContext;
+ @Mock private DisplayManager mDisplayManager;
+
+ private Injector mInjector;
+
+ @Before
+ public void setFixture() {
+ mInjector = new Injector(mContext);
+
+ when(mContext.getSystemService(DisplayManager.class)).thenReturn(mDisplayManager);
+ }
+
+ @Override
+ protected void initializeSession(StaticMockitoSessionBuilder builder) {
+ builder.spyStatic(UserManager.class);
+ }
+
+ @Test
+ public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_notSupported() {
+ mockUmIsUsersOnSecondaryDisplaysEnabled(false);
+
+ int [] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+ assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+ .that(displayIds).isNull();
+ }
+
+ @Test
+ public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_noDisplaysAtAll() {
+ mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+ mockGetDisplays();
+
+ int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+ assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+ .that(displayIds).isNull();
+ }
+
+ @Test
+ public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_defaultDisplayOnly() {
+ mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+ mockGetDisplays(mDefaultDisplay);
+
+ int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+ assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+ .that(displayIds).isNull();
+ }
+
+ @Test
+ public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_noDefaultDisplay() {
+ mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+ mockGetDisplays(validDisplay(42));
+
+ int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+ assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+ .that(displayIds).isNull();
+ }
+
+ @Test
+ public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_mixed() {
+ mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+ mockGetDisplays(mDefaultDisplay, validDisplay(42), invalidDisplay(108));
+
+ int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+ assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+ .that(displayIds).isNotNull();
+ assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+ .that(displayIds).asList().containsExactly(42);
+ }
+
+ // Extra test to make sure the array is properly copied...
+ @Test
+ public void testGetSecondaryDisplayIdsForStartingBackgroundUsers_mixed_invalidFirst() {
+ mockUmIsUsersOnSecondaryDisplaysEnabled(true);
+ mockGetDisplays(invalidDisplay(108), mDefaultDisplay, validDisplay(42));
+
+ int[] displayIds = mInjector.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+ assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+ .that(displayIds).asList().containsExactly(42);
+ }
+
+ private Display validDisplay(int displayId) {
+ return mockDisplay(displayId, /* valid= */ true);
+ }
+
+ private Display invalidDisplay(int displayId) {
+ return mockDisplay(displayId, /* valid= */ false);
+ }
+
+ private Display mockDisplay(int displayId, boolean valid) {
+ Display display = mock(Display.class);
+
+ when(display.getDisplayId()).thenReturn(displayId);
+ when(display.isValid()).thenReturn(valid);
+
+ return display;
+ }
+
+ private void mockGetDisplays(Display... displays) {
+ Log.d(TAG, "mockGetDisplays(): " + Arrays.toString(displays));
+ when(mDisplayManager.getDisplays()).thenReturn(displays);
+ }
+
+ private void mockUmIsUsersOnSecondaryDisplaysEnabled(boolean enabled) {
+ Log.d(TAG, "Mocking UserManager.isUsersOnSecondaryDisplaysEnabled() to return " + enabled);
+ doReturn(enabled).when(() -> UserManager.isUsersOnSecondaryDisplaysEnabled());
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index cf5d1133b741..89accf877f99 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -42,6 +43,7 @@ import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.app.BroadcastOptions;
import android.app.IApplicationThread;
+import android.app.RemoteServiceException.CannotDeliverBroadcastException;
import android.app.usage.UsageEvents.Event;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
@@ -56,6 +58,7 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.os.Binder;
import android.os.Bundle;
+import android.os.DeadObjectException;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -136,6 +139,12 @@ public class BroadcastQueueTest {
private BroadcastQueue mQueue;
/**
+ * When enabled {@link ActivityManagerService#startProcessLocked} will fail
+ * by returning {@code null}; otherwise it will spawn a new mock process.
+ */
+ private boolean mFailStartProcess;
+
+ /**
* Map from PID to registered registered runtime receivers.
*/
private SparseArray<ReceiverList> mRegisteredReceivers = new SparseArray<>();
@@ -185,10 +194,13 @@ public class BroadcastQueueTest {
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting startProcessLocked() for "
+ Arrays.toString(invocation.getArguments()));
+ if (mFailStartProcess) {
+ return null;
+ }
final String processName = invocation.getArgument(0);
final ApplicationInfo ai = invocation.getArgument(1);
- final ProcessRecord res = makeActiveProcessRecord(ai, processName, false,
- false, UnaryOperator.identity());
+ final ProcessRecord res = makeActiveProcessRecord(ai, processName,
+ ProcessBehavior.NORMAL, UnaryOperator.identity());
mHandlerThread.getThreadHandler().post(() -> {
synchronized (mAms) {
mQueue.onApplicationAttachedLocked(res);
@@ -197,6 +209,16 @@ public class BroadcastQueueTest {
return res;
}).when(mAms).startProcessLocked(any(), any(), anyBoolean(), anyInt(),
any(), anyInt(), anyBoolean(), anyBoolean());
+ doAnswer((invocation) -> {
+ final String processName = invocation.getArgument(0);
+ final int uid = invocation.getArgument(1);
+ for (ProcessRecord r : mActiveProcesses) {
+ if (Objects.equals(r.processName, processName) && r.uid == uid) {
+ return r;
+ }
+ }
+ return null;
+ }).when(mAms).getProcessRecordLocked(any(), anyInt());
doNothing().when(mAms).appNotResponding(any(), any());
final BroadcastConstants constants = new BroadcastConstants(
@@ -278,29 +300,51 @@ public class BroadcastQueueTest {
}
}
+ private enum ProcessBehavior {
+ /** Process broadcasts normally */
+ NORMAL,
+ /** Wedge and never confirm broadcast receipt */
+ WEDGE,
+ /** Process broadcast by requesting abort */
+ ABORT,
+ /** Appear to behave completely dead */
+ DEAD,
+ }
+
private ProcessRecord makeActiveProcessRecord(String packageName) throws Exception {
final ApplicationInfo ai = makeApplicationInfo(packageName);
- return makeActiveProcessRecord(ai, ai.processName, false, false,
+ return makeActiveProcessRecord(ai, ai.processName, ProcessBehavior.NORMAL,
UnaryOperator.identity());
}
- private ProcessRecord makeWedgedActiveProcessRecord(String packageName) throws Exception {
+ private ProcessRecord makeActiveProcessRecord(String packageName,
+ ProcessBehavior behavior) throws Exception {
final ApplicationInfo ai = makeApplicationInfo(packageName);
- return makeActiveProcessRecord(ai, ai.processName, true, false,
+ return makeActiveProcessRecord(ai, ai.processName, behavior,
UnaryOperator.identity());
}
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, String processName,
- boolean wedged, boolean abort, UnaryOperator<Bundle> extrasOperator) throws Exception {
+ ProcessBehavior behavior, UnaryOperator<Bundle> extrasOperator) throws Exception {
+ final boolean wedge = (behavior == ProcessBehavior.WEDGE);
+ final boolean abort = (behavior == ProcessBehavior.ABORT);
+ final boolean dead = (behavior == ProcessBehavior.DEAD);
+
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, processName, ai.uid));
r.setPid(mNextPid.getAndIncrement());
mActiveProcesses.add(r);
- final IApplicationThread thread = mock(IApplicationThread.class);
+ final IApplicationThread thread;
+ if (dead) {
+ thread = mock(IApplicationThread.class, (invocation) -> {
+ throw new DeadObjectException();
+ });
+ } else {
+ thread = mock(IApplicationThread.class);
+ }
final IBinder threadBinder = new Binder();
doReturn(threadBinder).when(thread).asBinder();
r.makeActive(thread, mAms.mProcessStats);
- doReturn(r).when(mAms).getProcessRecordLocked(eq(r.info.processName), eq(r.info.uid));
final IIntentReceiver receiver = mock(IIntentReceiver.class);
final IBinder receiverBinder = new Binder();
@@ -310,17 +354,28 @@ public class BroadcastQueueTest {
mRegisteredReceivers.put(r.getPid(), receiverList);
doAnswer((invocation) -> {
+ Log.v(TAG, "Intercepting killLocked() for "
+ + Arrays.toString(invocation.getArguments()));
+ mActiveProcesses.remove(r);
+ mRegisteredReceivers.remove(r.getPid());
+ return invocation.callRealMethod();
+ }).when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean());
+
+ // If we're entirely dead, rely on default behaviors above
+ if (dead) return r;
+
+ doAnswer((invocation) -> {
Log.v(TAG, "Intercepting scheduleReceiver() for "
+ Arrays.toString(invocation.getArguments()));
final Bundle extras = invocation.getArgument(5);
- if (!wedged) {
+ if (!wedge) {
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
!= ProcessList.SCHED_GROUP_UNDEFINED);
mHandlerThread.getThreadHandler().post(() -> {
synchronized (mAms) {
- mQueue.finishReceiverLocked(r, Activity.RESULT_OK,
- null, extrasOperator.apply(extras), abort, false);
+ mQueue.finishReceiverLocked(r, Activity.RESULT_OK, null,
+ extrasOperator.apply(extras), abort, false);
}
});
}
@@ -333,7 +388,7 @@ public class BroadcastQueueTest {
+ Arrays.toString(invocation.getArguments()));
final Bundle extras = invocation.getArgument(4);
final boolean ordered = invocation.getArgument(5);
- if (!wedged && ordered) {
+ if (!wedge && ordered) {
assertTrue(r.mReceivers.numberOfCurReceivers() > 0);
assertTrue(mQueue.getPreferredSchedulingGroupLocked(r)
!= ProcessList.SCHED_GROUP_UNDEFINED);
@@ -449,6 +504,13 @@ public class BroadcastQueueTest {
any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt());
}
+ private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent)
+ throws Exception {
+ verify(app.getThread(), mode).scheduleReceiver(
+ argThat(filterEqualsIgnoringComponent(intent)), any(), any(), anyInt(), any(),
+ any(), eq(false), eq(UserHandle.USER_SYSTEM), anyInt());
+ }
+
private void verifyScheduleReceiver(VerificationMode mode, ProcessRecord app, Intent intent,
ComponentName component) throws Exception {
final Intent targetedIntent = new Intent(intent);
@@ -689,7 +751,8 @@ public class BroadcastQueueTest {
@Test
public void testWedged() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
- final ProcessRecord receiverApp = makeWedgedActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+ ProcessBehavior.WEDGE);
final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
@@ -700,6 +763,106 @@ public class BroadcastQueueTest {
}
/**
+ * Verify that we handle registered receivers in a process that always
+ * responds with {@link DeadObjectException}, recovering to restart the
+ * process and deliver their next broadcast.
+ */
+ @Test
+ public void testDead_Registered() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+ ProcessBehavior.DEAD);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeRegisteredReceiver(receiverApp))));
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+ waitForIdle();
+
+ // First broadcast should have already been dead
+ verifyScheduleRegisteredReceiver(receiverApp, airplane);
+ verify(receiverApp).scheduleCrashLocked(any(),
+ eq(CannotDeliverBroadcastException.TYPE_ID), any());
+
+ // Second broadcast in new process should work fine
+ final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ assertNotEquals(receiverApp, restartedReceiverApp);
+ verifyScheduleReceiver(restartedReceiverApp, timezone);
+ }
+
+ /**
+ * Verify that we handle manifest receivers in a process that always
+ * responds with {@link DeadObjectException}, recovering to restart the
+ * process and deliver their next broadcast.
+ */
+ @Test
+ public void testDead_Manifest() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN,
+ ProcessBehavior.DEAD);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))));
+ waitForIdle();
+
+ // First broadcast should have already been dead
+ verifyScheduleReceiver(receiverApp, airplane);
+ verify(receiverApp).scheduleCrashLocked(any(),
+ eq(CannotDeliverBroadcastException.TYPE_ID), any());
+
+ // Second broadcast in new process should work fine
+ final ProcessRecord restartedReceiverApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ assertNotEquals(receiverApp, restartedReceiverApp);
+ verifyScheduleReceiver(restartedReceiverApp, timezone);
+ }
+
+ /**
+ * Verify that we handle the system failing to start a process.
+ */
+ @Test
+ public void testFailStartProcess() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+ // Send broadcast while process starts are failing
+ mFailStartProcess = true;
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+
+ // Confirm that queue goes idle, with no processes
+ waitForIdle();
+ assertEquals(1, mActiveProcesses.size());
+
+ // Send more broadcasts with working process starts
+ mFailStartProcess = false;
+ final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+ enqueueBroadcast(makeBroadcastRecord(timezone, callerApp,
+ List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
+ makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW))));
+
+ // Confirm that we only saw second broadcast
+ waitForIdle();
+ assertEquals(3, mActiveProcesses.size());
+ final ProcessRecord receiverGreenApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ final ProcessRecord receiverYellowApp = mAms.getProcessRecordLocked(PACKAGE_YELLOW,
+ getUidForPackage(PACKAGE_YELLOW));
+ verifyScheduleReceiver(never(), receiverGreenApp, airplane);
+ verifyScheduleReceiver(never(), receiverYellowApp, airplane);
+ verifyScheduleReceiver(times(1), receiverGreenApp, timezone);
+ verifyScheduleReceiver(times(1), receiverYellowApp, timezone);
+ }
+
+ /**
* Verify that we cleanup a disabled component, skipping a pending dispatch
* of broadcast to that component.
*/
@@ -742,6 +905,45 @@ public class BroadcastQueueTest {
}
/**
+ * Verify that killing a running process skips registered receivers.
+ */
+ @Test
+ public void testKill() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord oldApp = makeActiveProcessRecord(PACKAGE_GREEN);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ try (SyncBarrier b = new SyncBarrier()) {
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>(
+ List.of(makeRegisteredReceiver(oldApp),
+ makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)))));
+
+ synchronized (mAms) {
+ oldApp.killLocked(TAG, 42, false);
+ mQueue.onApplicationCleanupLocked(oldApp);
+ }
+ }
+ waitForIdle();
+
+ // Confirm that we cold-started after the kill
+ final ProcessRecord newApp = mAms.getProcessRecordLocked(PACKAGE_GREEN,
+ getUidForPackage(PACKAGE_GREEN));
+ assertNotEquals(oldApp, newApp);
+
+ // Confirm that we saw no registered receiver traffic
+ final IApplicationThread oldThread = oldApp.getThread();
+ verify(oldThread, never()).scheduleRegisteredReceiver(any(),
+ any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+ final IApplicationThread newThread = newApp.getThread();
+ verify(newThread, never()).scheduleRegisteredReceiver(any(),
+ any(), anyInt(), any(), any(), anyBoolean(), anyBoolean(), anyInt(), anyInt());
+
+ // Confirm that we saw final manifest broadcast
+ verifyScheduleReceiver(times(1), newApp, airplane,
+ new ComponentName(PACKAGE_GREEN, CLASS_GREEN));
+ }
+
+ /**
* Verify that we skip broadcasts to an app being backed up.
*/
@Test
@@ -770,13 +972,13 @@ public class BroadcastQueueTest {
// Purposefully warm-start the middle apps to make sure we dispatch to
// both cold and warm apps in expected order
makeActiveProcessRecord(makeApplicationInfo(PACKAGE_BLUE), PACKAGE_BLUE,
- false, false, (extras) -> {
+ ProcessBehavior.NORMAL, (extras) -> {
extras = clone(extras);
extras.putBoolean(PACKAGE_BLUE, true);
return extras;
});
makeActiveProcessRecord(makeApplicationInfo(PACKAGE_YELLOW), PACKAGE_YELLOW,
- false, false, (extras) -> {
+ ProcessBehavior.NORMAL, (extras) -> {
extras = clone(extras);
extras.putBoolean(PACKAGE_YELLOW, true);
return extras;
@@ -858,7 +1060,7 @@ public class BroadcastQueueTest {
// Create a process that aborts any ordered broadcasts
makeActiveProcessRecord(makeApplicationInfo(PACKAGE_GREEN), PACKAGE_GREEN,
- false, true, (extras) -> {
+ ProcessBehavior.ABORT, (extras) -> {
extras = clone(extras);
extras.putBoolean(PACKAGE_GREEN, true);
return extras;
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
index 86e126475654..e1713b0beb77 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsUidStateTrackerTest.java
@@ -34,12 +34,9 @@ import static android.app.AppOpsManager.UID_STATE_TOP;
import static com.android.server.appop.AppOpsUidStateTracker.processStateToUidState;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
@@ -49,25 +46,22 @@ import static org.mockito.Mockito.verify;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.Message;
import android.util.SparseArray;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.os.Clock;
import com.android.server.appop.AppOpsUidStateTracker.UidStateChangedCallback;
+import com.android.server.appop.AppOpsUidStateTrackerImpl.DelayableExecutor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.quality.Strictness;
-import java.util.concurrent.atomic.AtomicLong;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.PriorityQueue;
public class AppOpsUidStateTrackerTest {
@@ -81,12 +75,11 @@ public class AppOpsUidStateTrackerTest {
ActivityManagerInternal mAmi;
@Mock
- Handler mHandler;
-
- @Mock
AppOpsService.Constants mConstants;
- AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock();
+ AppOpsUidStateTrackerTestExecutor mExecutor = new AppOpsUidStateTrackerTestExecutor();
+
+ AppOpsUidStateTrackerTestClock mClock = new AppOpsUidStateTrackerTestClock(mExecutor);
AppOpsUidStateTracker mIntf;
@@ -101,7 +94,8 @@ public class AppOpsUidStateTrackerTest {
mConstants.TOP_STATE_SETTLE_TIME = 10 * 1000L;
mConstants.FG_SERVICE_STATE_SETTLE_TIME = 5 * 1000L;
mConstants.BG_STATE_SETTLE_TIME = 1 * 1000L;
- mIntf = new AppOpsUidStateTrackerImpl(mAmi, mHandler, mClock, mConstants);
+ mIntf = new AppOpsUidStateTrackerImpl(mAmi, mExecutor, mClock, mConstants,
+ Thread.currentThread());
}
@After
@@ -263,18 +257,10 @@ public class AppOpsUidStateTrackerTest {
// Still in foreground due to settle time
assertForeground(UID);
- AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
- AtomicLong delayAtomicReference = new AtomicLong();
-
- getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference);
- Message message = messageAtomicReference.get();
- long delay = delayAtomicReference.get();
-
- assertNotNull(message);
- assertEquals(mConstants.TOP_STATE_SETTLE_TIME + 1, delay);
+ mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+ assertForeground(UID);
- mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
- message.getCallback().run();
+ mClock.advanceTime(1);
assertBackground(UID);
}
@@ -291,18 +277,10 @@ public class AppOpsUidStateTrackerTest {
// Still in foreground due to settle time
assertForeground(UID);
- AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
- AtomicLong delayAtomicReference = new AtomicLong();
-
- getPostDelayedMessageArguments(messageAtomicReference, delayAtomicReference);
- Message message = messageAtomicReference.get();
- long delay = delayAtomicReference.get();
-
- assertNotNull(message);
- assertEquals(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1, delay);
+ mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1);
+ assertForeground(UID);
- mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME + 1);
- message.getCallback().run();
+ mClock.advanceTime(1);
assertBackground(UID);
}
@@ -319,14 +297,8 @@ public class AppOpsUidStateTrackerTest {
// Still in foreground due to settle time
assertForeground(UID);
- AtomicReference<Message> messageAtomicReference = new AtomicReference<>();
-
- getPostDelayedMessageArguments(messageAtomicReference, null);
- Message message = messageAtomicReference.get();
-
// 1 ms short of settle time
mClock.advanceTime(mConstants.FG_SERVICE_STATE_SETTLE_TIME - 1);
- message.getCallback().run();
assertForeground(UID);
}
@@ -471,8 +443,6 @@ public class AppOpsUidStateTrackerTest {
.topState()
.update();
- getLatestPostMessageArgument().getCallback().run();
-
verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_TOP), eq(true));
}
@@ -484,8 +454,6 @@ public class AppOpsUidStateTrackerTest {
.foregroundServiceState()
.update();
- getLatestPostMessageArgument().getCallback().run();
-
verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND_SERVICE), eq(true));
}
@@ -497,8 +465,6 @@ public class AppOpsUidStateTrackerTest {
.foregroundState()
.update();
- getLatestPostMessageArgument().getCallback().run();
-
verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_FOREGROUND), eq(true));
}
@@ -510,8 +476,6 @@ public class AppOpsUidStateTrackerTest {
.backgroundState()
.update();
- getLatestPostMessageArgument().getCallback().run();
-
verify(cb).onUidStateChanged(eq(UID), eq(UID_STATE_BACKGROUND), eq(false));
}
@@ -679,7 +643,6 @@ public class AppOpsUidStateTrackerTest {
.nonExistentState()
.update();
- verify(mHandler, never()).post(any());
verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
}
@@ -695,7 +658,6 @@ public class AppOpsUidStateTrackerTest {
.nonExistentState()
.update();
- getLatestPostMessageArgument().getCallback().run();
verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(false));
}
@@ -711,7 +673,6 @@ public class AppOpsUidStateTrackerTest {
.nonExistentState()
.update();
- getLatestPostMessageArgument().getCallback().run();
verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
}
@@ -727,7 +688,6 @@ public class AppOpsUidStateTrackerTest {
.nonExistentState()
.update();
- getLatestPostMessageArgument().getCallback().run();
verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
}
@@ -743,10 +703,32 @@ public class AppOpsUidStateTrackerTest {
.nonExistentState()
.update();
- getLatestPostMessageArgument().getCallback().run();
verify(cb, atLeastOnce()).onUidStateChanged(eq(UID), eq(UID_STATE_CACHED), eq(true));
}
+ @Test
+ public void testUidStateChangedBackgroundThenForegroundImmediately() {
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ UidStateChangedCallback cb = addUidStateChangeCallback();
+
+ procStateBuilder(UID)
+ .backgroundState()
+ .update();
+
+ mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME - 1);
+
+ procStateBuilder(UID)
+ .topState()
+ .update();
+
+ mClock.advanceTime(1);
+
+ verify(cb, never()).onUidStateChanged(anyInt(), anyInt(), anyBoolean());
+ }
+
public void testUidStateChangedCallback(int initialState, int finalState) {
int initialUidState = processStateToUidState(initialState);
int finalUidState = processStateToUidState(finalState);
@@ -767,13 +749,9 @@ public class AppOpsUidStateTrackerTest {
.update();
if (finalUidStateIsBackgroundAndLessImportant) {
- AtomicReference<Message> delayedMessage = new AtomicReference<>();
- getPostDelayedMessageArguments(delayedMessage, new AtomicLong());
mClock.advanceTime(mConstants.TOP_STATE_SETTLE_TIME + 1);
- delayedMessage.get().getCallback().run();
}
- getLatestPostMessageArgument().getCallback().run();
verify(cb, atLeastOnce())
.onUidStateChanged(eq(UID), eq(finalUidState), eq(foregroundChange));
}
@@ -781,7 +759,7 @@ public class AppOpsUidStateTrackerTest {
private UidStateChangedCallback addUidStateChangeCallback() {
UidStateChangedCallback cb =
Mockito.mock(UidStateChangedCallback.class);
- mIntf.addUidStateChangedCallback(mHandler, cb);
+ mIntf.addUidStateChangedCallback(r -> r.run(), cb);
return cb;
}
@@ -795,30 +773,6 @@ public class AppOpsUidStateTrackerTest {
assertEquals(MODE_IGNORED, mIntf.evalMode(uid, OP_NO_CAPABILITIES, MODE_FOREGROUND));
}
- private void getPostDelayedMessageArguments(AtomicReference<Message> message,
- AtomicLong delay) {
-
- ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
- ArgumentCaptor<Long> delayCaptor = ArgumentCaptor.forClass(Long.class);
-
- verify(mHandler).sendMessageDelayed(messageCaptor.capture(), delayCaptor.capture());
-
- if (message != null) {
- message.set(messageCaptor.getValue());
- }
- if (delay != null) {
- delay.set(delayCaptor.getValue());
- }
- }
-
- private Message getLatestPostMessageArgument() {
- ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
-
- verify(mHandler, atLeast(1)).sendMessage(messageCaptor.capture());
-
- return messageCaptor.getValue();
- }
-
private UidProcStateUpdateBuilder procStateBuilder(int uid) {
return new UidProcStateUpdateBuilder(mIntf, uid);
}
@@ -896,8 +850,14 @@ public class AppOpsUidStateTrackerTest {
private static class AppOpsUidStateTrackerTestClock extends Clock {
+ private AppOpsUidStateTrackerTestExecutor mExecutor;
long mElapsedRealTime = 0x5f3759df;
+ AppOpsUidStateTrackerTestClock(AppOpsUidStateTrackerTestExecutor executor) {
+ mExecutor = executor;
+ executor.setUptime(mElapsedRealTime);
+ }
+
@Override
public long elapsedRealtime() {
return mElapsedRealTime;
@@ -905,6 +865,53 @@ public class AppOpsUidStateTrackerTest {
void advanceTime(long time) {
mElapsedRealTime += time;
+ mExecutor.setUptime(mElapsedRealTime); // assume uptime == elapsedtime
+ }
+ }
+
+ private static class AppOpsUidStateTrackerTestExecutor implements DelayableExecutor {
+
+ private static class QueueElement implements Comparable<QueueElement> {
+
+ private long mExecutionTime;
+ private Runnable mRunnable;
+
+ private QueueElement(long executionTime, Runnable runnable) {
+ mExecutionTime = executionTime;
+ mRunnable = runnable;
+ }
+
+ @Override
+ public int compareTo(QueueElement queueElement) {
+ return Long.compare(mExecutionTime, queueElement.mExecutionTime);
+ }
+ }
+
+ private long mUptime = 0;
+
+ private PriorityQueue<QueueElement> mDelayedMessages = new PriorityQueue();
+
+ @Override
+ public void execute(Runnable runnable) {
+ runnable.run();
+ }
+
+ @Override
+ public void executeDelayed(Runnable runnable, long delay) {
+ if (delay <= 0) {
+ execute(runnable);
+ }
+
+ mDelayedMessages.add(new QueueElement(mUptime + delay, runnable));
+ }
+
+ private void setUptime(long uptime) {
+ while (!mDelayedMessages.isEmpty()
+ && mDelayedMessages.peek().mExecutionTime <= uptime) {
+ mDelayedMessages.poll().mRunnable.run();
+ }
+
+ mUptime = uptime;
}
}
}
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 19b798da5aab..b7bbcd7562cd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -134,15 +134,36 @@ 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.getMinSatiatedBalance(0, pkgRestricted));
assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
- assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
- 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,
mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance(0, pkgExempted));
+
+ final String pkgUpdater = "com.pkg.updater";
+ when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
+ assertEquals(5 * EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES
+ + EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance(0, pkgUpdater));
+ // Make sure it doesn't suggest a min balance greater than max.
+ final int updateCount = (int) (EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES
+ / EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES);
+ when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater)))
+ .thenReturn(updateCount);
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
+
+ assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
+ mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
assertEquals(EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES,
mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
}
@@ -152,8 +173,10 @@ public class JobSchedulerEconomicPolicyTest {
setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(25));
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
- setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9));
- setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+ arcToCake(1));
assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
@@ -163,8 +186,12 @@ public class JobSchedulerEconomicPolicyTest {
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));
- assertEquals(arcToCake(7), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ assertEquals(arcToCake(6), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
+ assertEquals(arcToCake(4), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ final String pkgUpdater = "com.pkg.updater";
+ when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(3);
+ assertEquals(arcToCake(4) + 3 * arcToCake(1),
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
}
@Test
@@ -175,6 +202,8 @@ public class JobSchedulerEconomicPolicyTest {
setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
+ setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER,
+ arcToCake(-4));
assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
@@ -186,6 +215,10 @@ public class JobSchedulerEconomicPolicyTest {
when(mIrs.isPackageExempted(anyInt(), eq(pkgExempted))).thenReturn(true);
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
+ final String pkgUpdater = "com.pkg.updater";
+ when(mIrs.getAppUpdateResponsibilityCount(anyInt(), eq(pkgUpdater))).thenReturn(5);
+ assertEquals(arcToCake(0) + 5 * arcToCake(0),
+ mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
// Test min+max reversed.
setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 3c1710250803..0d6f3267f41d 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -37,11 +37,14 @@ import static com.android.server.am.ProcessList.NETWORK_STATE_BLOCK;
import static com.android.server.am.ProcessList.NETWORK_STATE_NO_CHANGE;
import static com.android.server.am.ProcessList.NETWORK_STATE_UNBLOCK;
+import static com.google.common.truth.Truth.assertWithMessage;
+
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.assertNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
@@ -62,7 +65,6 @@ import android.app.SyncNotedAppOp;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
@@ -74,6 +76,8 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.util.IntArray;
+import android.util.Log;
+import android.util.Pair;
import androidx.test.filters.MediumTest;
import androidx.test.filters.SmallTest;
@@ -116,6 +120,7 @@ public class ActivityManagerServiceTest {
private static final String TAG = ActivityManagerServiceTest.class.getSimpleName();
private static final int TEST_UID = 11111;
+ private static final int USER_ID = 666;
private static final long TEST_PROC_STATE_SEQ1 = 555;
private static final long TEST_PROC_STATE_SEQ2 = 556;
@@ -147,8 +152,8 @@ public class ActivityManagerServiceTest {
@Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
private Context mContext = getInstrumentation().getTargetContext();
+
@Mock private AppOpsService mAppOpsService;
- @Mock private PackageManager mPackageManager;
private TestInjector mInjector;
private ActivityManagerService mAms;
@@ -828,6 +833,57 @@ public class ActivityManagerServiceTest {
true); // expectWait
}
+ @Test
+ public void testGetSecondaryDisplayIdsForStartingBackgroundUsers() {
+ mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{4, 8, 15, 16, 23, 42};
+
+ int [] displayIds = mAms.getSecondaryDisplayIdsForStartingBackgroundUsers();
+
+ assertWithMessage("mAms.getSecondaryDisplayIdsForStartingBackgroundUsers()")
+ .that(displayIds).asList().containsExactly(4, 8, 15, 16, 23, 42);
+ }
+
+ @Test
+ public void testStartUserInBackgroundOnSecondaryDisplay_invalidDisplay() {
+ mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{4, 8, 15, 16, 23, 42};
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 666));
+
+ assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+ .that(mInjector.usersStartedOnSecondaryDisplays).isEmpty();
+ }
+
+ @Test
+ public void testStartUserInBackgroundOnSecondaryDisplay_validDisplay_failed() {
+ mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{ 4, 8, 15, 16, 23, 42 };
+ mInjector.returnValueForstartUserOnSecondaryDisplay = false;
+
+ boolean started = mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 42);
+ Log.v(TAG, "Started: " + started);
+
+ assertWithMessage("mAms.startUserInBackgroundOnSecondaryDisplay(%s, 42)", USER_ID)
+ .that(started).isFalse();
+ assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+ .that(mInjector.usersStartedOnSecondaryDisplays)
+ .containsExactly(new Pair<>(USER_ID, 42));
+ }
+
+ @Test
+ public void testStartUserInBackgroundOnSecondaryDisplay_validDisplay_success() {
+ mInjector.secondaryDisplayIdsForStartingBackgroundUsers = new int[]{ 4, 8, 15, 16, 23, 42 };
+ mInjector.returnValueForstartUserOnSecondaryDisplay = true;
+
+ boolean started = mAms.startUserInBackgroundOnSecondaryDisplay(USER_ID, 42);
+ Log.v(TAG, "Started: " + started);
+
+ assertWithMessage("mAms.startUserInBackgroundOnSecondaryDisplay(%s, 42)", USER_ID)
+ .that(started).isTrue();
+ assertWithMessage("UserController.startUserOnSecondaryDisplay() calls")
+ .that(mInjector.usersStartedOnSecondaryDisplays)
+ .containsExactly(new Pair<>(USER_ID, 42));
+ }
+
private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq,
long lastNetworkUpdatedProcStateSeq,
final long procStateSeqToWait, boolean expectWait) throws Exception {
@@ -922,7 +978,11 @@ public class ActivityManagerServiceTest {
}
private class TestInjector extends Injector {
- private boolean mRestricted = true;
+ public boolean restricted = true;
+ public int[] secondaryDisplayIdsForStartingBackgroundUsers;
+
+ public boolean returnValueForstartUserOnSecondaryDisplay;
+ public List<Pair<Integer, Integer>> usersStartedOnSecondaryDisplays = new ArrayList<>();
TestInjector(Context context) {
super(context);
@@ -940,11 +1000,18 @@ public class ActivityManagerServiceTest {
@Override
public boolean isNetworkRestrictedForUid(int uid) {
- return mRestricted;
+ return restricted;
}
- public void setNetworkRestrictedForUid(boolean restricted) {
- mRestricted = restricted;
+ @Override
+ public int[] getSecondaryDisplayIdsForStartingBackgroundUsers() {
+ return secondaryDisplayIdsForStartingBackgroundUsers;
+ }
+
+ @Override
+ public boolean startUserOnSecondaryDisplay(int userId, int displayId) {
+ usersStartedOnSecondaryDisplays.add(new Pair<>(userId, displayId));
+ return returnValueForstartUserOnSecondaryDisplay;
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
index 5d9d7656aa5b..6b8c26d7b1d4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java
@@ -25,7 +25,6 @@ import static org.mockito.Mockito.verify;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
-import android.hardware.input.InputManagerInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -39,6 +38,7 @@ import android.view.WindowManager;
import androidx.test.InstrumentationRegistry;
import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index ef203d03fd98..57ded9905273 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -56,7 +56,6 @@ import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.hardware.display.DisplayManagerInternal;
import android.hardware.input.IInputManager;
-import android.hardware.input.InputManagerInternal;
import android.hardware.input.VirtualKeyEvent;
import android.hardware.input.VirtualMouseButtonEvent;
import android.hardware.input.VirtualMouseRelativeEvent;
@@ -84,6 +83,7 @@ import androidx.test.InstrumentationRegistry;
import com.android.internal.app.BlockedAppStreamingActivity;
import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
import org.junit.Before;
import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 8280fc6c962f..fc2a4cf3a7d8 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -38,7 +38,6 @@ import android.hardware.SensorManager;
import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
import android.os.Handler;
import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -56,7 +55,6 @@ import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public class AutomaticBrightnessControllerTest {
private static final float BRIGHTNESS_MIN_FLOAT = 0.0f;
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index 6a6cd6c914a2..800f60bec828 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -35,7 +35,6 @@ import android.os.PowerManager;
import android.os.Temperature;
import android.os.Temperature.ThrottlingStatus;
import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -57,7 +56,6 @@ import java.util.ArrayList;
import java.util.List;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public class BrightnessThrottlerTest {
private static final float EPSILON = 0.000001f;
diff --git a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java b/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
index 26a83a23de33..53d8de0c2bbb 100644
--- a/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/ColorFadeTest.java
@@ -24,7 +24,6 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.hardware.display.DisplayManagerInternal;
-import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -39,7 +38,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public class ColorFadeTest {
private static final int DISPLAY_ID = 123;
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 66420ad4572e..04702c448152 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -44,7 +43,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public final class DisplayDeviceConfigTest {
private DisplayDeviceConfig mDisplayDeviceConfig;
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 3eb1dea80c7f..3c7bb2ac51d6 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -53,12 +53,10 @@ import android.hardware.display.DisplayedContentSamplingAttributes;
import android.hardware.display.IDisplayManagerCallback;
import android.hardware.display.IVirtualDisplayCallback;
import android.hardware.display.VirtualDisplayConfig;
-import android.hardware.input.InputManagerInternal;
import android.os.Handler;
import android.os.IBinder;
import android.os.MessageQueue;
import android.os.Process;
-import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayEventReceiver;
@@ -78,6 +76,7 @@ import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.display.DisplayManagerService.SyncRoot;
+import com.android.server.input.InputManagerInternal;
import com.android.server.lights.LightsManager;
import com.android.server.sensors.SensorManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -106,7 +105,6 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.LongStream;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public class DisplayManagerServiceTest {
private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;
diff --git a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
index 53fa3e2db376..a1e5ce74014b 100644
--- a/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/HighBrightnessModeControllerTest.java
@@ -22,14 +22,10 @@ import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
import static android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
-
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED;
import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
-import static com.android.server.display.AutomaticBrightnessController
- .AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
-
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
import static com.android.server.display.DisplayDeviceConfig.HDR_PERCENT_OF_SCREEN_REQUIRED_DEFAULT;
-
import static com.android.server.display.HighBrightnessModeController.HBM_TRANSITION_POINT_INVALID;
import static org.junit.Assert.assertEquals;
@@ -51,7 +47,6 @@ import android.os.PowerManager;
import android.os.Temperature;
import android.os.Temperature.ThrottlingStatus;
import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
import android.test.mock.MockContentResolver;
import android.util.MathUtils;
@@ -76,7 +71,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public class HighBrightnessModeControllerTest {
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index cc68ba88f76e..0b33c30fd7e8 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -45,7 +45,6 @@ import android.os.IThermalService;
import android.os.PowerManager;
import android.os.Process;
import android.os.test.TestLooper;
-import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.DisplayAddress;
import android.view.DisplayInfo;
@@ -67,7 +66,6 @@ import java.util.Arrays;
import java.util.Set;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public class LogicalDisplayMapperTest {
private static int sUniqueTestDisplayId = 0;
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index b0738fdb78d0..5a43530d44dd 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -26,7 +26,6 @@ import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
import android.graphics.Point;
-import android.platform.test.annotations.Presubmit;
import android.view.DisplayInfo;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -40,7 +39,6 @@ import java.io.InputStream;
import java.io.OutputStream;
@SmallTest
-@Presubmit
public class LogicalDisplayTest {
private static final int DISPLAY_ID = 0;
private static final int LAYER_STACK = 0;
diff --git a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
index 9f1a209d2ee1..92d8abd4f173 100644
--- a/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
+++ b/services/tests/servicestests/src/com/android/server/display/TEST_MAPPING
@@ -3,18 +3,10 @@
{
"name": "FrameworksServicesTests",
"options": [
- {
- "include-filter": "com.android.server.display."
- },
- {
- "include-annotation": "android.platform.test.annotations.Presubmit"
- },
- {
- "exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-annotation": "org.junit.Ignore"
- }
+ {"include-filter": "com.android.server.display"},
+ {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+ {"exclude-annotation": "org.junit.Ignore"}
]
}
]
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 f69c5c2df6ce..fabf535b729a 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
@@ -19,7 +19,6 @@ package com.android.server.display.brightness;
import static org.junit.Assert.assertEquals;
import android.hardware.display.BrightnessInfo;
-import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -29,7 +28,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public final class BrightnessEventTest {
private BrightnessEvent mBrightnessEvent;
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
index ffc2e0dfe26c..57aa61a0ebd2 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/BrightnessReasonTest.java
@@ -18,7 +18,6 @@ package com.android.server.display.brightness;
import static org.junit.Assert.assertEquals;
-import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -28,7 +27,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
@SmallTest
-@Presubmit
@RunWith(AndroidJUnit4.class)
public final class BrightnessReasonTest {
private BrightnessReason mBrightnessReason;
diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
index 844f5d4cd3eb..e390bccf41d8 100644
--- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt
@@ -19,13 +19,14 @@ package com.android.server.input
import android.content.Context
import android.content.ContextWrapper
import android.hardware.display.DisplayViewport
-import android.hardware.input.InputManagerInternal
import android.os.IInputConstants
import android.os.test.TestLooper
import android.platform.test.annotations.Presubmit
import android.view.Display
import android.view.PointerIcon
import androidx.test.InstrumentationRegistry
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -35,7 +36,6 @@ import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.never
@@ -43,9 +43,8 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.TimeUnit
/**
* Tests for {@link InputManagerService}.
diff --git a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
index 90b19a450f48..04ba7d3df38d 100644
--- a/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/DeviceStateProviderImplTest.java
@@ -35,13 +35,13 @@ import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
-import android.hardware.input.InputManagerInternal;
import androidx.annotation.NonNull;
import com.android.server.LocalServices;
import com.android.server.devicestate.DeviceState;
import com.android.server.devicestate.DeviceStateProvider;
+import com.android.server.input.InputManagerInternal;
import org.junit.After;
import org.junit.Before;
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 2918365b94c8..107bbe1c79e3 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -79,7 +79,10 @@
<activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity"
android:turnScreenOn="true"
android:showWhenLocked="true" />
- <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity" />
+ <activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity"
+ android:theme="@style/WhiteBackgroundTheme"
+ android:turnScreenOn="true"
+ android:showWhenLocked="true"/>
<activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/>
<service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService"
diff --git a/services/tests/wmtests/res/values/styles.xml b/services/tests/wmtests/res/values/styles.xml
new file mode 100644
index 000000000000..6857ff99e9b8
--- /dev/null
+++ b/services/tests/wmtests/res/values/styles.xml
@@ -0,0 +1,24 @@
+<!--
+ ~ 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>
+ <style name="WhiteBackgroundTheme" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:windowNoTitle">true</item>
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ </style>
+</resources>
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index fe46c148c677..a76b82babe08 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -58,7 +58,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerInternal;
-import android.hardware.input.InputManagerInternal;
import android.media.AudioManagerInternal;
import android.os.Handler;
import android.os.HandlerThread;
@@ -76,6 +75,7 @@ import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.accessibility.AccessibilityShortcutController;
import com.android.server.GestureLauncherService;
import com.android.server.LocalServices;
+import com.android.server.input.InputManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.vr.VrManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
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 333be7bd8c61..2f23e7fac75f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2990,7 +2990,8 @@ public class ActivityRecordTests extends WindowTestsBase {
.setSystemDecorations(true).build();
// Add a decor insets provider window.
final WindowState navbar = createNavBarWithProvidedInsets(squareDisplay);
- squareDisplay.getDisplayPolicy().updateDecorInsetsInfoIfNeeded(navbar);
+ assertTrue(navbar.providesNonDecorInsets()
+ && squareDisplay.getDisplayPolicy().updateDecorInsetsInfo());
squareDisplay.sendNewConfiguration();
final Task task = new TaskBuilder(mSupervisor).setDisplay(squareDisplay).build();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index e2c94c5b4b3d..49fd1ab60e09 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -296,6 +296,14 @@ public class ContentRecorderTests extends WindowTestsBase {
}
@Test
+ public void testRemoveTask_stopsRecording_nullSessionShouldNotThrowExceptions() {
+ mContentRecorder.setContentRecordingSession(mTaskSession);
+ mContentRecorder.updateRecording();
+ mContentRecorder.setContentRecordingSession(null);
+ mTask.removeImmediately();
+ }
+
+ @Test
public void testUpdateMirroredSurface_capturedAreaResized() {
mContentRecorder.setContentRecordingSession(mDisplaySession);
mContentRecorder.updateRecording();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 262b141e6005..a980765711d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -292,12 +292,16 @@ public class DisplayPolicyTests extends WindowTestsBase {
final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy();
final DisplayInfo di = mDisplayContent.getDisplayInfo();
final int prevScreenHeightDp = mDisplayContent.getConfiguration().screenHeightDp;
- assertTrue(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
+ assertTrue(navbar.providesNonDecorInsets() && displayPolicy.updateDecorInsetsInfo());
assertEquals(NAV_BAR_HEIGHT, displayPolicy.getDecorInsetsInfo(di.rotation,
di.logicalWidth, di.logicalHeight).mConfigInsets.bottom);
mDisplayContent.sendNewConfiguration();
assertNotEquals(prevScreenHeightDp, mDisplayContent.getConfiguration().screenHeightDp);
- assertFalse(displayPolicy.updateDecorInsetsInfoIfNeeded(navbar));
+ assertFalse(navbar.providesNonDecorInsets() && displayPolicy.updateDecorInsetsInfo());
+
+ navbar.removeIfPossible();
+ assertEquals(0, displayPolicy.getDecorInsetsInfo(di.rotation, di.logicalWidth,
+ di.logicalHeight).mNonDecorInsets.bottom);
}
@SetupWindows(addWindows = { W_NAVIGATION_BAR, W_INPUT_METHOD })
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index adf694c2a88d..170b3881edd7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1532,7 +1532,7 @@ public class RecentTasksTest extends WindowTestsBase {
@Override
void getTasks(int maxNum, List<RunningTaskInfo> list, int flags, RecentTasks recentTasks,
- WindowContainer root, int callingUid, ArraySet<Integer> profileIds) {
+ WindowContainer<?> root, int callingUid, ArraySet<Integer> profileIds) {
mLastAllowed = (flags & FLAG_ALLOWED) == FLAG_ALLOWED;
super.getTasks(maxNum, list, flags, recentTasks, root, callingUid, profileIds);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
index 0b58428e9bfb..736f8f7a5ed3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ScreenshotTests.java
@@ -16,6 +16,10 @@
package com.android.server.wm;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.statusBars;
+
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertNotNull;
@@ -23,20 +27,33 @@ import static org.junit.Assert.assertTrue;
import android.app.Activity;
import android.app.Instrumentation;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.ColorSpace;
import android.graphics.GraphicBuffer;
+import android.graphics.Insets;
import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.DataSpace;
+import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
+import android.os.ServiceManager;
import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+import android.view.IWindowManager;
import android.view.PointerIcon;
import android.view.SurfaceControl;
-import android.view.WindowManager;
+import android.view.cts.surfacevalidator.BitmapPixelChecker;
+import android.view.cts.surfacevalidator.PixelColor;
+import android.view.cts.surfacevalidator.SaveBitmapHelper;
import android.window.ScreenCapture;
+import android.window.ScreenCapture.ScreenCaptureListener;
+import android.window.ScreenCapture.ScreenshotHardwareBuffer;
+import android.window.ScreenCapture.ScreenshotSync;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -45,6 +62,7 @@ import androidx.test.rule.ActivityTestRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestName;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -60,6 +78,8 @@ public class ScreenshotTests {
private static final int BUFFER_HEIGHT = 100;
private final Instrumentation mInstrumentation = getInstrumentation();
+ @Rule
+ public TestName mTestName = new TestName();
@Rule
public ActivityTestRule<ScreenshotActivity> mActivityRule =
@@ -95,8 +115,8 @@ public class ScreenshotTests {
buffer.unlockCanvasAndPost(canvas);
t.show(secureSC)
- .setBuffer(secureSC, buffer)
- .setColorSpace(secureSC, ColorSpace.get(ColorSpace.Named.SRGB))
+ .setBuffer(secureSC, HardwareBuffer.createFromGraphicBuffer(buffer))
+ .setDataSpace(secureSC, DataSpace.DATASPACE_SRGB)
.apply(true);
ScreenCapture.LayerCaptureArgs args = new ScreenCapture.LayerCaptureArgs.Builder(secureSC)
@@ -112,15 +132,70 @@ public class ScreenshotTests {
Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
screenshot.recycle();
- int numMatchingPixels = PixelChecker.getNumMatchingPixels(swBitmap,
- new PixelColor(PixelColor.RED));
- long sizeOfBitmap = swBitmap.getWidth() * swBitmap.getHeight();
+ BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
+ Rect bounds = new Rect(0, 0, swBitmap.getWidth(), swBitmap.getHeight());
+ int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
+ int sizeOfBitmap = bounds.width() * bounds.height();
boolean success = numMatchingPixels == sizeOfBitmap;
swBitmap.recycle();
assertTrue(success);
}
+ @Test
+ public void testCaptureDisplay() throws Exception {
+ IWindowManager windowManager = IWindowManager.Stub.asInterface(
+ ServiceManager.getService(Context.WINDOW_SERVICE));
+ SurfaceControl sc = new SurfaceControl.Builder()
+ .setName("Layer")
+ .setCallsite("testCaptureDisplay")
+ .build();
+
+ SurfaceControl.Transaction t = mActivity.addChildSc(sc);
+ mInstrumentation.waitForIdleSync();
+
+ GraphicBuffer buffer = GraphicBuffer.create(BUFFER_WIDTH, BUFFER_HEIGHT,
+ PixelFormat.RGBA_8888,
+ GraphicBuffer.USAGE_HW_TEXTURE | GraphicBuffer.USAGE_HW_COMPOSER
+ | GraphicBuffer.USAGE_SW_WRITE_RARELY);
+
+ Canvas canvas = buffer.lockCanvas();
+ canvas.drawColor(Color.RED);
+ buffer.unlockCanvasAndPost(canvas);
+
+ Point point = mActivity.getPositionBelowStatusBar();
+ t.show(sc)
+ .setBuffer(sc, HardwareBuffer.createFromGraphicBuffer(buffer))
+ .setDataSpace(sc, DataSpace.DATASPACE_SRGB)
+ .setPosition(sc, point.x, point.y)
+ .apply(true);
+
+ Pair<ScreenCaptureListener, ScreenshotSync> syncScreenCapture =
+ ScreenCapture.createSyncCaptureListener();
+ windowManager.captureDisplay(DEFAULT_DISPLAY, null, syncScreenCapture.first);
+ ScreenshotHardwareBuffer hardwareBuffer = syncScreenCapture.second.get();
+ assertNotNull(hardwareBuffer);
+
+ Bitmap screenshot = hardwareBuffer.asBitmap();
+ assertNotNull(screenshot);
+
+ Bitmap swBitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
+ screenshot.recycle();
+
+ BitmapPixelChecker bitmapPixelChecker = new BitmapPixelChecker(PixelColor.RED);
+ Rect bounds = new Rect(point.x, point.y, BUFFER_WIDTH + point.x, BUFFER_HEIGHT + point.y);
+ int numMatchingPixels = bitmapPixelChecker.getNumMatchingPixels(swBitmap, bounds);
+ int pixelMatchSize = bounds.width() * bounds.height();
+ boolean success = numMatchingPixels == pixelMatchSize;
+
+ if (!success) {
+ SaveBitmapHelper.saveBitmap(swBitmap, getClass(), mTestName, "failedImage");
+ }
+ swBitmap.recycle();
+ assertTrue("numMatchingPixels=" + numMatchingPixels + " pixelMatchSize=" + pixelMatchSize,
+ success);
+ }
+
public static class ScreenshotActivity extends Activity {
private static final long WAIT_TIMEOUT_S = 5;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -130,7 +205,6 @@ public class ScreenshotTests {
super.onCreate(savedInstanceState);
getWindow().getDecorView().setPointerIcon(
PointerIcon.getSystemIcon(this, PointerIcon.TYPE_NULL));
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
SurfaceControl.Transaction addChildSc(SurfaceControl surfaceControl) {
@@ -148,88 +222,14 @@ public class ScreenshotTests {
}
return t;
}
- }
- public abstract static class PixelChecker {
- static int getNumMatchingPixels(Bitmap bitmap, PixelColor pixelColor) {
- int numMatchingPixels = 0;
- for (int x = 0; x < bitmap.getWidth(); x++) {
- for (int y = 0; y < bitmap.getHeight(); y++) {
- int color = bitmap.getPixel(x, y);
- if (matchesColor(pixelColor, color)) {
- numMatchingPixels++;
- }
- }
- }
- return numMatchingPixels;
- }
-
- static boolean matchesColor(PixelColor expectedColor, int color) {
- final float red = Color.red(color);
- final float green = Color.green(color);
- final float blue = Color.blue(color);
- final float alpha = Color.alpha(color);
-
- return alpha <= expectedColor.mMaxAlpha
- && alpha >= expectedColor.mMinAlpha
- && red <= expectedColor.mMaxRed
- && red >= expectedColor.mMinRed
- && green <= expectedColor.mMaxGreen
- && green >= expectedColor.mMinGreen
- && blue <= expectedColor.mMaxBlue
- && blue >= expectedColor.mMinBlue;
- }
- }
-
- public static class PixelColor {
- public static final int BLACK = 0xFF000000;
- public static final int RED = 0xFF0000FF;
- public static final int GREEN = 0xFF00FF00;
- public static final int BLUE = 0xFFFF0000;
- public static final int YELLOW = 0xFF00FFFF;
- public static final int MAGENTA = 0xFFFF00FF;
- public static final int WHITE = 0xFFFFFFFF;
-
- public static final int TRANSPARENT_RED = 0x7F0000FF;
- public static final int TRANSPARENT_BLUE = 0x7FFF0000;
- public static final int TRANSPARENT = 0x00000000;
-
- // Default to black
- public short mMinAlpha;
- public short mMaxAlpha;
- public short mMinRed;
- public short mMaxRed;
- public short mMinBlue;
- public short mMaxBlue;
- public short mMinGreen;
- public short mMaxGreen;
-
- public PixelColor(int color) {
- short alpha = (short) ((color >> 24) & 0xFF);
- short blue = (short) ((color >> 16) & 0xFF);
- short green = (short) ((color >> 8) & 0xFF);
- short red = (short) (color & 0xFF);
-
- mMinAlpha = (short) getMinValue(alpha);
- mMaxAlpha = (short) getMaxValue(alpha);
- mMinRed = (short) getMinValue(red);
- mMaxRed = (short) getMaxValue(red);
- mMinBlue = (short) getMinValue(blue);
- mMaxBlue = (short) getMaxValue(blue);
- mMinGreen = (short) getMinValue(green);
- mMaxGreen = (short) getMaxValue(green);
- }
-
- public PixelColor() {
- this(BLACK);
- }
-
- private int getMinValue(short color) {
- return Math.max(color - 4, 0);
- }
+ public Point getPositionBelowStatusBar() {
+ Insets statusBarInsets = getWindow()
+ .getDecorView()
+ .getRootWindowInsets()
+ .getInsets(statusBars() | displayCutout());
- private int getMaxValue(short color) {
- return Math.min(color + 4, 0xFF);
+ return new Point(statusBarInsets.left, statusBarInsets.top);
}
}
}
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 3331839193f8..51894f32883d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -735,6 +735,11 @@ public class TransitionTests extends WindowTestsBase {
assertTrue(asyncRotationController.isTargetToken(decorToken));
assertShouldFreezeInsetsPosition(asyncRotationController, statusBar, true);
+ if (TransitionController.SYNC_METHOD != BLASTSyncEngine.METHOD_BLAST) {
+ // Only seamless window syncs its draw transaction with transition.
+ assertFalse(asyncRotationController.handleFinishDrawing(statusBar, mMockT));
+ assertTrue(asyncRotationController.handleFinishDrawing(screenDecor, mMockT));
+ }
screenDecor.setOrientationChanging(false);
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
// executed until the transaction is committed, so it is still in target tokens.
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 46b4b76dc12f..8b63904baf31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -41,6 +41,7 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
@@ -68,6 +69,7 @@ import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
import android.window.ClientWindowFrames;
+import android.window.ScreenCapture;
import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -423,6 +425,45 @@ public class WindowManagerServiceTests extends WindowTestsBase {
LETTERBOX_BACKGROUND_SOLID_COLOR)).isFalse();
}
+ @Test
+ public void testCaptureDisplay() {
+ Rect displayBounds = new Rect(0, 0, 100, 200);
+ spyOn(mDisplayContent);
+ when(mDisplayContent.getBounds()).thenReturn(displayBounds);
+
+ // Null captureArgs
+ ScreenCapture.LayerCaptureArgs resultingArgs =
+ mWm.getCaptureArgs(DEFAULT_DISPLAY, null /* captureArgs */);
+ assertEquals(displayBounds, resultingArgs.mSourceCrop);
+
+ // Non null captureArgs, didn't set rect
+ ScreenCapture.CaptureArgs captureArgs = new ScreenCapture.CaptureArgs.Builder<>().build();
+ resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
+ assertEquals(displayBounds, resultingArgs.mSourceCrop);
+
+ // Non null captureArgs, invalid rect
+ captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+ .setSourceCrop(new Rect(0, 0, -1, -1))
+ .build();
+ resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
+ assertEquals(displayBounds, resultingArgs.mSourceCrop);
+
+ // Non null captureArgs, null rect
+ captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+ .setSourceCrop(null)
+ .build();
+ resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
+ assertEquals(displayBounds, resultingArgs.mSourceCrop);
+
+ // Non null captureArgs, valid rect
+ Rect validRect = new Rect(0, 0, 10, 50);
+ captureArgs = new ScreenCapture.CaptureArgs.Builder<>()
+ .setSourceCrop(validRect)
+ .build();
+ resultingArgs = mWm.getCaptureArgs(DEFAULT_DISPLAY, captureArgs);
+ assertEquals(validRect, resultingArgs.mSourceCrop);
+ }
+
private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
when(remoteToken.toWindowContainerToken()).thenReturn(wct);
diff --git a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
index eafcef2f1d38..1e74451a8d4d 100644
--- a/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
+++ b/services/translation/java/com/android/server/translation/TranslationManagerServiceImpl.java
@@ -210,21 +210,15 @@ final class TranslationManagerServiceImpl extends
final int translatedAppUid =
getAppUidByComponentName(getContext(), componentName, getUserId());
final String packageName = componentName.getPackageName();
- if (activityDestroyed) {
- // In the Activity destroy case, we only calls onTranslationFinished() in
- // non-finisTranslation() state. If there is a finisTranslation() calls by apps, we
- // should remove the waiting callback to avoid callback twice.
+ // In the Activity destroyed case, we only call onTranslationFinished() in
+ // non-finishTranslation() state. If there is a finishTranslation() call by apps, we
+ // should remove the waiting callback to avoid invoking callbacks twice.
+ if (activityDestroyed || mWaitingFinishedCallbackActivities.contains(token)) {
invokeCallbacks(STATE_UI_TRANSLATION_FINISHED,
/* sourceSpec= */ null, /* targetSpec= */ null,
packageName, translatedAppUid);
mWaitingFinishedCallbackActivities.remove(token);
- } else {
- if (mWaitingFinishedCallbackActivities.contains(token)) {
- invokeCallbacks(STATE_UI_TRANSLATION_FINISHED,
- /* sourceSpec= */ null, /* targetSpec= */ null,
- packageName, translatedAppUid);
- mWaitingFinishedCallbackActivities.remove(token);
- }
+ mActiveTranslations.remove(token);
}
}
@@ -237,6 +231,9 @@ final class TranslationManagerServiceImpl extends
// Activity is the new Activity, the original Activity is paused in the same task.
// To make sure the operation still work, we use the token to find the target Activity in
// this task, not the top Activity only.
+ //
+ // Note: getAttachedNonFinishingActivityForTask() takes the shareable activity token. We
+ // call this method so that we can get the regular activity token below.
ActivityTokens candidateActivityTokens =
mActivityTaskManagerInternal.getAttachedNonFinishingActivityForTask(taskId, token);
if (candidateActivityTokens == null) {
@@ -263,27 +260,27 @@ final class TranslationManagerServiceImpl extends
getAppUidByComponentName(getContext(), componentName, getUserId());
String packageName = componentName.getPackageName();
- invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, activityToken,
+ invokeCallbacksIfNecessaryLocked(state, sourceSpec, targetSpec, packageName, token,
translatedAppUid);
- updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, activityToken,
+ updateActiveTranslationsLocked(state, sourceSpec, targetSpec, packageName, token,
translatedAppUid);
}
@GuardedBy("mLock")
private void updateActiveTranslationsLocked(int state, TranslationSpec sourceSpec,
- TranslationSpec targetSpec, String packageName, IBinder activityToken,
+ TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken,
int translatedAppUid) {
// We keep track of active translations and their state so that we can:
// 1. Trigger callbacks that are registered after translation has started.
// See registerUiTranslationStateCallbackLocked().
// 2. NOT trigger callbacks when the state didn't change.
// See invokeCallbacksIfNecessaryLocked().
- ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken);
+ ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken);
switch (state) {
case STATE_UI_TRANSLATION_STARTED: {
if (activeTranslation == null) {
try {
- activityToken.linkToDeath(this, /* flags= */ 0);
+ shareableActivityToken.linkToDeath(this, /* flags= */ 0);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call linkToDeath for translated app with uid="
+ translatedAppUid + "; activity is already dead", e);
@@ -294,7 +291,7 @@ final class TranslationManagerServiceImpl extends
packageName, translatedAppUid);
return;
}
- mActiveTranslations.put(activityToken,
+ mActiveTranslations.put(shareableActivityToken,
new ActiveTranslation(sourceSpec, targetSpec, translatedAppUid,
packageName));
}
@@ -317,7 +314,7 @@ final class TranslationManagerServiceImpl extends
case STATE_UI_TRANSLATION_FINISHED: {
if (activeTranslation != null) {
- mActiveTranslations.remove(activityToken);
+ mActiveTranslations.remove(shareableActivityToken);
}
break;
}
@@ -332,12 +329,12 @@ final class TranslationManagerServiceImpl extends
@GuardedBy("mLock")
private void invokeCallbacksIfNecessaryLocked(int state, TranslationSpec sourceSpec,
- TranslationSpec targetSpec, String packageName, IBinder activityToken,
+ TranslationSpec targetSpec, String packageName, IBinder shareableActivityToken,
int translatedAppUid) {
boolean shouldInvokeCallbacks = true;
int stateForCallbackInvocation = state;
- ActiveTranslation activeTranslation = mActiveTranslations.get(activityToken);
+ ActiveTranslation activeTranslation = mActiveTranslations.get(shareableActivityToken);
if (activeTranslation == null) {
if (state != STATE_UI_TRANSLATION_STARTED) {
shouldInvokeCallbacks = false;
@@ -403,14 +400,6 @@ final class TranslationManagerServiceImpl extends
}
}
- if (DEBUG) {
- Slog.d(TAG,
- (shouldInvokeCallbacks ? "" : "NOT ")
- + "Invoking callbacks for translation state="
- + stateForCallbackInvocation + " for app with uid=" + translatedAppUid
- + " packageName=" + packageName);
- }
-
if (shouldInvokeCallbacks) {
invokeCallbacks(stateForCallbackInvocation, sourceSpec, targetSpec, packageName,
translatedAppUid);
@@ -448,7 +437,7 @@ final class TranslationManagerServiceImpl extends
pw.println(waitingFinishCallbackSize);
for (IBinder activityToken : mWaitingFinishedCallbackActivities) {
pw.print(prefix);
- pw.print("activityToken: ");
+ pw.print("shareableActivityToken: ");
pw.println(activityToken);
}
}
@@ -458,7 +447,14 @@ final class TranslationManagerServiceImpl extends
int state, TranslationSpec sourceSpec, TranslationSpec targetSpec, String packageName,
int translatedAppUid) {
Bundle result = createResultForCallback(state, sourceSpec, targetSpec, packageName);
- if (mCallbacks.getRegisteredCallbackCount() == 0) {
+ int registeredCallbackCount = mCallbacks.getRegisteredCallbackCount();
+ if (DEBUG) {
+ Slog.d(TAG, "Invoking " + registeredCallbackCount + " callbacks for translation state="
+ + state + " for app with uid=" + translatedAppUid
+ + " packageName=" + packageName);
+ }
+
+ if (registeredCallbackCount == 0) {
return;
}
List<InputMethodInfo> enabledInputMethods = getEnabledInputMethods();
@@ -521,8 +517,10 @@ final class TranslationManagerServiceImpl extends
@GuardedBy("mLock")
public void registerUiTranslationStateCallbackLocked(IRemoteCallback callback, int sourceUid) {
mCallbacks.register(callback, sourceUid);
-
- if (mActiveTranslations.size() == 0) {
+ int numActiveTranslations = mActiveTranslations.size();
+ Slog.i(TAG, "New registered callback for sourceUid=" + sourceUid + " with currently "
+ + numActiveTranslations + " active translations");
+ if (numActiveTranslations == 0) {
return;
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 91b868dd3803..be7c1122ae6e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -136,9 +136,6 @@ public class VoiceInteractionManagerService extends SystemService {
private final RemoteCallbackList<IVoiceInteractionSessionListener>
mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
- // TODO(b/226201975): remove once RoleService supports pre-created users
- private final ArrayList<UserHandle> mIgnoredPreCreatedUsers = new ArrayList<>();
-
public VoiceInteractionManagerService(Context context) {
super(context);
mContext = context;
@@ -309,24 +306,14 @@ public class VoiceInteractionManagerService extends SystemService {
return hotwordDetectionConnection.mIdentity;
}
+ // TODO(b/226201975): remove this method once RoleService supports pre-created users
@Override
public void onPreCreatedUserConversion(int userId) {
- Slogf.d(TAG, "onPreCreatedUserConversion(%d)", userId);
-
- for (int i = 0; i < mIgnoredPreCreatedUsers.size(); i++) {
- UserHandle preCreatedUser = mIgnoredPreCreatedUsers.get(i);
- if (preCreatedUser.getIdentifier() == userId) {
- Slogf.d(TAG, "Updating role on pre-created user %d", userId);
- mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
- preCreatedUser);
- mIgnoredPreCreatedUsers.remove(i);
- return;
- }
- }
- Slogf.w(TAG, "onPreCreatedUserConversion(%d): not available on "
- + "mIgnoredPreCreatedUserIds (%s)", userId, mIgnoredPreCreatedUsers);
+ Slogf.d(TAG, "onPreCreatedUserConversion(%d): calling onRoleHoldersChanged() again",
+ userId);
+ mServiceStub.mRoleObserver.onRoleHoldersChanged(RoleManager.ROLE_ASSISTANT,
+ UserHandle.of(userId));
}
-
}
// implementation entry point and binder service
@@ -809,8 +796,10 @@ public class VoiceInteractionManagerService extends SystemService {
if (TextUtils.isEmpty(curInteractor)) {
return null;
}
- if (DEBUG) Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+ if (DEBUG) {
+ Slog.d(TAG, "getCurInteractor curInteractor=" + curInteractor
+ " user=" + userHandle);
+ }
return ComponentName.unflattenFromString(curInteractor);
}
@@ -818,8 +807,9 @@ public class VoiceInteractionManagerService extends SystemService {
Settings.Secure.putStringForUser(mContext.getContentResolver(),
Settings.Secure.VOICE_INTERACTION_SERVICE,
comp != null ? comp.flattenToShortString() : "", userHandle);
- if (DEBUG) Slog.d(TAG, "setCurInteractor comp=" + comp
- + " user=" + userHandle);
+ if (DEBUG) {
+ Slog.d(TAG, "setCurInteractor comp=" + comp + " user=" + userHandle);
+ }
}
ComponentName findAvailRecognizer(String prefPackage, int userHandle) {
@@ -1917,7 +1907,6 @@ public class VoiceInteractionManagerService extends SystemService {
pw.println(" mTemporarilyDisabled: " + mTemporarilyDisabled);
pw.println(" mCurUser: " + mCurUser);
pw.println(" mCurUserSupported: " + mCurUserSupported);
- pw.println(" mIgnoredPreCreatedUsers: " + mIgnoredPreCreatedUsers);
dumpSupportedUsers(pw, " ");
mDbHelper.dump(pw);
if (mImpl == null) {
@@ -2031,6 +2020,11 @@ public class VoiceInteractionManagerService extends SystemService {
List<String> roleHolders = mRm.getRoleHoldersAsUser(roleName, user);
+ if (DEBUG) {
+ Slogf.d(TAG, "onRoleHoldersChanged(%s, %s): roleHolders=%s", roleName, user,
+ roleHolders);
+ }
+
// TODO(b/226201975): this method is beling called when a pre-created user is added,
// at which point it doesn't have any role holders. But it's not called again when
// the actual user is added (i.e., when the pre-created user is converted), so we
@@ -2041,9 +2035,9 @@ public class VoiceInteractionManagerService extends SystemService {
if (roleHolders.isEmpty()) {
UserInfo userInfo = mUserManagerInternal.getUserInfo(user.getIdentifier());
if (userInfo != null && userInfo.preCreated) {
- Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now",
- userInfo.toFullString());
- mIgnoredPreCreatedUsers.add(user);
+ Slogf.d(TAG, "onRoleHoldersChanged(): ignoring pre-created user %s for now,"
+ + " this method will be called again when it's converted to a real"
+ + " user", userInfo.toFullString());
return;
}
}
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 1d6798b7fc6e..a0467c26897f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -856,6 +856,31 @@ public final class NetworkRegistrationInfo implements Parcelable {
public Builder() {}
/**
+ * Builder from the existing {@link NetworkRegistrationInfo}.
+ *
+ * @param nri The network registration info object.
+ * @hide
+ */
+ public Builder(@NonNull NetworkRegistrationInfo nri) {
+ mDomain = nri.mDomain;
+ mTransportType = nri.mTransportType;
+ mInitialRegistrationState = nri.mInitialRegistrationState;
+ mAccessNetworkTechnology = nri.mAccessNetworkTechnology;
+ mRejectCause = nri.mRejectCause;
+ mEmergencyOnly = nri.mEmergencyOnly;
+ mAvailableServices = new ArrayList<>(nri.mAvailableServices);
+ mCellIdentity = nri.mCellIdentity;
+ if (nri.mDataSpecificInfo != null) {
+ mDataSpecificRegistrationInfo = new DataSpecificRegistrationInfo(
+ nri.mDataSpecificInfo);
+ }
+ if (nri.mVoiceSpecificInfo != null) {
+ mVoiceSpecificRegistrationInfo = new VoiceSpecificRegistrationInfo(
+ nri.mVoiceSpecificInfo);
+ }
+ }
+
+ /**
* Set the network domain.
*
* @param domain Network domain.
diff --git a/tests/Codegen/Android.bp b/tests/Codegen/Android.bp
index ddbf16817b94..7fbe3b37f99e 100644
--- a/tests/Codegen/Android.bp
+++ b/tests/Codegen/Android.bp
@@ -24,6 +24,14 @@ android_test {
plugins: [
"staledataclass-annotation-processor",
],
+ // Exports needed for staledataclass-annotation-processor, see b/139342589.
+ javacflags: [
+ "-J--add-modules=jdk.compiler",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ ],
static_libs: [
"junit",
"hamcrest",
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 4d801c9032cb..8d4da8a013cf 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -24,6 +24,7 @@ import androidx.test.uiautomator.Until
import com.android.server.wm.flicker.testapp.ActivityOptions
import com.android.server.wm.traces.common.Rect
import com.android.server.wm.traces.common.WindowManagerConditionsFactory
+import com.android.server.wm.traces.common.region.Region
import com.android.server.wm.traces.parser.toFlickerComponent
import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
@@ -178,6 +179,20 @@ open class PipAppHelper(instrumentation: Instrumentation) : StandardAppHelper(
wmHelper.StateSyncBuilder()
.withAppTransitionIdle()
.waitForAndVerify()
+ waitForPipWindowToExpandFrom(wmHelper, Region.from(windowRect))
+ }
+
+ private fun waitForPipWindowToExpandFrom(
+ wmHelper: WindowManagerStateHelper,
+ windowRect: Region
+ ) {
+ wmHelper.StateSyncBuilder().add("pipWindowExpanded") {
+ val pipAppWindow = it.wmState.visibleWindows.firstOrNull { window ->
+ this.windowMatchesAnyOf(window)
+ } ?: return@add false
+ val pipRegion = pipAppWindow.frameRegion
+ return@add pipRegion.coversMoreThan(windowRect)
+ }.waitForAndVerify()
}
companion object {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
index 77f28f60d2cc..2babf1c8e982 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromNotificationWarm.kt
@@ -19,6 +19,7 @@ package com.android.server.wm.flicker.launch
import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.RequiresDevice
+import android.view.Surface
import android.view.WindowInsets
import android.view.WindowManager
import androidx.test.uiautomator.By
@@ -74,6 +75,13 @@ open class OpenAppFromNotificationWarm(
.withFullScreenApp(testApp)
.waitForAndVerify()
testApp.postNotification(wmHelper)
+
+ if (testSpec.isTablet) {
+ tapl.setExpectedRotation(testSpec.startRotation)
+ } else {
+ tapl.setExpectedRotation(Surface.ROTATION_0)
+ }
+
tapl.goHome()
wmHelper.StateSyncBuilder()
.withHomeActivityVisible()
diff --git a/tests/HandwritingIme/OWNERS b/tests/HandwritingIme/OWNERS
new file mode 100644
index 000000000000..6bb4b17ed4eb
--- /dev/null
+++ b/tests/HandwritingIme/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 34867
+
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
index dc34cb6d6a53..39fe84bb8d4c 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java
@@ -29,6 +29,8 @@ import android.view.inputmethod.DeleteGesture;
import android.view.inputmethod.HandwritingGesture;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InsertGesture;
+import android.view.inputmethod.JoinOrSplitGesture;
+import android.view.inputmethod.RemoveSpaceGesture;
import android.view.inputmethod.SelectGesture;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
@@ -38,6 +40,7 @@ import android.widget.Spinner;
import android.widget.Toast;
import java.util.Random;
+import java.util.function.IntConsumer;
public class HandwritingIme extends InputMethodService {
@@ -47,8 +50,9 @@ public class HandwritingIme extends InputMethodService {
private static final int OP_SELECT = 1;
private static final int OP_DELETE = 2;
private static final int OP_INSERT = 3;
+ private static final int OP_REMOVE_SPACE = 4;
+ private static final int OP_JOIN_OR_SPLIT = 5;
- private Window mInkWindow;
private InkView mInk;
static final String TAG = "HandwritingIme";
@@ -58,6 +62,7 @@ public class HandwritingIme extends InputMethodService {
private Spinner mRichGestureGranularitySpinner;
private PointF mRichGestureStartPoint;
+ private final IntConsumer mResultConsumer = value -> Log.d(TAG, "Gesture result: " + value);
interface HandwritingFinisher {
void finish();
@@ -93,29 +98,45 @@ public class HandwritingIme extends InputMethodService {
HandwritingGesture gesture = null;
switch(mRichGestureMode) {
case OP_SELECT:
- SelectGesture.Builder builder = new SelectGesture.Builder();
- builder.setGranularity(mRichGestureGranularity)
+ gesture = new SelectGesture.Builder()
+ .setGranularity(mRichGestureGranularity)
.setSelectionArea(new RectF(mRichGestureStartPoint.x,
mRichGestureStartPoint.y, event.getX(), event.getY()))
- .setFallbackText("fallback text");
- gesture = builder.build();
+ .setFallbackText("fallback text")
+ .build();
break;
case OP_DELETE:
- DeleteGesture.Builder builder1 = new DeleteGesture.Builder();
- builder1.setGranularity(mRichGestureGranularity)
+ gesture = new DeleteGesture.Builder()
+ .setGranularity(mRichGestureGranularity)
.setDeletionArea(new RectF(mRichGestureStartPoint.x,
mRichGestureStartPoint.y, event.getX(), event.getY()))
- .setFallbackText("fallback text");
- gesture = builder1.build();
+ .setFallbackText("fallback text")
+ .build();
break;
case OP_INSERT:
- InsertGesture.Builder builder2 = new InsertGesture.Builder();
- builder2.setInsertionPoint(
- new PointF(mRichGestureStartPoint.x, mRichGestureStartPoint.y))
+ gesture = new InsertGesture.Builder()
+ .setInsertionPoint(new PointF(
+ mRichGestureStartPoint.x, mRichGestureStartPoint.y))
.setTextToInsert(" ")
- .setFallbackText("fallback text");
- gesture = builder2.build();
-
+ .setFallbackText("fallback text")
+ .build();
+ break;
+ case OP_REMOVE_SPACE:
+ gesture = new RemoveSpaceGesture.Builder()
+ .setPoints(
+ new PointF(mRichGestureStartPoint.x,
+ mRichGestureStartPoint.y),
+ new PointF(event.getX(), event.getY()))
+ .setFallbackText("fallback text")
+ .build();
+ break;
+ case OP_JOIN_OR_SPLIT:
+ gesture = new JoinOrSplitGesture.Builder()
+ .setJoinOrSplitPoint(new PointF(
+ mRichGestureStartPoint.x, mRichGestureStartPoint.y))
+ .setFallbackText("fallback text")
+ .build();
+ break;
}
if (gesture == null) {
// This shouldn't happen
@@ -124,7 +145,7 @@ public class HandwritingIme extends InputMethodService {
}
InputConnection ic = getCurrentInputConnection();
if (getCurrentInputStarted() && ic != null) {
- ic.performHandwritingGesture(gesture, null, null);
+ ic.performHandwritingGesture(gesture, Runnable::run, mResultConsumer);
} else {
// This shouldn't happen
Log.e(TAG, "No active InputConnection");
@@ -179,9 +200,14 @@ public class HandwritingIme extends InputMethodService {
mRichGestureModeSpinner = new Spinner(this);
mRichGestureModeSpinner.setPadding(100, 0, 100, 0);
mRichGestureModeSpinner.setTooltipText("Handwriting IME mode");
- String[] items =
- new String[] { "Handwriting IME - Rich gesture disabled", "Rich gesture SELECT",
- "Rich gesture DELETE", "Rich gesture INSERT" };
+ String[] items = new String[] {
+ "Handwriting IME - Rich gesture disabled",
+ "Rich gesture SELECT",
+ "Rich gesture DELETE",
+ "Rich gesture INSERT",
+ "Rich gesture REMOVE SPACE",
+ "Rich gesture JOIN OR SPLIT",
+ };
ArrayAdapter<String> adapter = new ArrayAdapter<>(this,
android.R.layout.simple_spinner_dropdown_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
@@ -191,7 +217,7 @@ public class HandwritingIme extends InputMethodService {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mRichGestureMode = position;
mRichGestureGranularitySpinner.setEnabled(
- mRichGestureMode != OP_INSERT && mRichGestureMode != OP_NONE);
+ mRichGestureMode == OP_SELECT || mRichGestureMode == OP_DELETE);
Log.d(TAG, "Setting RichGesture Mode " + mRichGestureMode);
}
@@ -247,8 +273,8 @@ public class HandwritingIme extends InputMethodService {
public boolean onStartStylusHandwriting() {
Log.d(TAG, "onStartStylusHandwriting ");
Toast.makeText(this, "START HW", Toast.LENGTH_SHORT).show();
- mInkWindow = getStylusHandwritingWindow();
- mInkWindow.setContentView(mInk, mInk.getLayoutParams());
+ Window inkWindow = getStylusHandwritingWindow();
+ inkWindow.setContentView(mInk, mInk.getLayoutParams());
return true;
}
diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
index 94b1f863f197..e94c79ecca00 100644
--- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
+++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java
@@ -19,13 +19,11 @@ package com.google.android.test.handwritingime;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Path;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -33,9 +31,8 @@ class InkView extends View {
private static final long FINISH_TIMEOUT = 1500;
private final HandwritingIme.HandwritingFinisher mHwCanceller;
private final HandwritingIme.StylusConsumer mConsumer;
- private final int mTopInset;
- private Paint mPaint;
- private Path mPath;
+ private final Paint mPaint;
+ private final Path mPath;
private float mX, mY;
private static final float STYLUS_MOVE_TOLERANCE = 1;
private Runnable mFinishRunnable;
@@ -59,12 +56,8 @@ class InkView extends View {
WindowManager wm = context.getSystemService(WindowManager.class);
WindowMetrics metrics = wm.getCurrentWindowMetrics();
- Insets insets = metrics.getWindowInsets()
- .getInsetsIgnoringVisibility(WindowInsets.Type.systemBars());
setLayoutParams(new ViewGroup.LayoutParams(
- metrics.getBounds().width() - insets.left - insets.right,
- metrics.getBounds().height() - insets.top - insets.bottom));
- mTopInset = insets.top;
+ metrics.getBounds().width(), metrics.getBounds().height()));
}
@Override
@@ -76,14 +69,12 @@ class InkView extends View {
}
private void stylusStart(float x, float y) {
- y = y - mTopInset;
mPath.moveTo(x, y);
mX = x;
mY = y;
}
private void stylusMove(float x, float y) {
- y = y - mTopInset;
float dx = Math.abs(x - mX);
float dy = Math.abs(y - mY);
if (mPath.isEmpty()) {
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index b7c4c5b28168..f2234fb64108 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -51,6 +51,7 @@ java_test_host {
data: [
":com.android.apex.apkrollback.test_v1",
":test.rebootless_apex_v1",
+ ":RollbackTest",
],
}
diff --git a/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java b/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
index 388548691b77..2001c04bd645 100644
--- a/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
+++ b/tests/benchmarks/internal/src/com/android/internal/LambdaPerfTest.java
@@ -1,454 +1,458 @@
-/*
- * 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.internal;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import android.app.Activity;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.Message;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.SystemClock;
-import android.util.Log;
-
-import androidx.test.filters.LargeTest;
-
-import com.android.internal.util.function.pooled.PooledConsumer;
-import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.internal.util.function.pooled.PooledPredicate;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runners.model.Statement;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Compares the performance of regular lambda and pooled lambda. */
-@LargeTest
-public class LambdaPerfTest {
- private static final boolean DEBUG = false;
- private static final String TAG = LambdaPerfTest.class.getSimpleName();
-
- private static final String LAMBDA_FORM_REGULAR = "regular";
- private static final String LAMBDA_FORM_POOLED = "pooled";
-
- private static final int WARMUP_ITERATIONS = 1000;
- private static final int TEST_ITERATIONS = 3000000;
- private static final int TASK_COUNT = 10;
- private static final long DELAY_AFTER_BENCH_MS = 1000;
-
- private String mMethodName;
-
- private final Bundle mTestResults = new Bundle();
- private final ArrayList<Task> mTasks = new ArrayList<>();
-
- // The member fields are used to ensure lambda capturing. They don't have the actual meaning.
- private final Task mTask = new Task();
- private final Rect mBounds = new Rect();
- private int mTaskId;
- private long mTime;
- private boolean mTop;
-
- @Rule
- public final TestRule mRule = (base, description) -> new Statement() {
- @Override
- public void evaluate() throws Throwable {
- mMethodName = description.getMethodName();
- mTasks.clear();
- for (int i = 0; i < TASK_COUNT; i++) {
- final Task t = new Task();
- mTasks.add(t);
- }
- base.evaluate();
-
- getInstrumentation().sendStatus(Activity.RESULT_OK, mTestResults);
- }
- };
-
- @Test
- public void test1ParamConsumer() {
- evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTask)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTask);
- forAllTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void test2PrimitiveParamsConsumer() {
- // Not in Integer#IntegerCache (-128~127) for autoboxing, that will create new object.
- mTaskId = 12345;
- mTime = 54321;
-
- evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTaskId, mTime)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTaskId, mTime);
- forAllTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void test3ParamsPredicate() {
- mTop = true;
- // In Integer#IntegerCache.
- mTaskId = 10;
-
- evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mBounds, mTop, mTaskId)));
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething,
- PooledLambda.__(Task.class), mBounds, mTop, mTaskId);
- handleTask(c);
- c.recycle();
- });
- }
-
- @Test
- public void testMessage() {
- evaluate(LAMBDA_FORM_REGULAR, () -> {
- final Message m = Message.obtain().setCallback(() -> mTask.doSomething(mTaskId, mTime));
- m.getCallback().run();
- m.recycle();
- });
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final Message m = PooledLambda.obtainMessage(Task::doSomething, mTask, mTaskId, mTime);
- m.getCallback().run();
- m.recycle();
- });
- }
-
- @Test
- public void testRunnable() {
- evaluate(LAMBDA_FORM_REGULAR, () -> {
- final Runnable r = mTask::doSomething;
- r.run();
- });
- evaluate(LAMBDA_FORM_POOLED, () -> {
- final Runnable r = PooledLambda.obtainRunnable(Task::doSomething, mTask).recycleOnUse();
- r.run();
- });
- }
-
- @Test
- public void testMultiThread() {
- final int numThread = 3;
-
- final Runnable regularAction = () -> forAllTask(t -> t.doSomething(mTask));
- final Runnable[] regularActions = new Runnable[numThread];
- Arrays.fill(regularActions, regularAction);
- evaluateMultiThread(LAMBDA_FORM_REGULAR, regularActions);
-
- final Runnable pooledAction = () -> {
- final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
- PooledLambda.__(Task.class), mTask);
- forAllTask(c);
- c.recycle();
- };
- final Runnable[] pooledActions = new Runnable[numThread];
- Arrays.fill(pooledActions, pooledAction);
- evaluateMultiThread(LAMBDA_FORM_POOLED, pooledActions);
- }
-
- private void forAllTask(Consumer<Task> callback) {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- callback.accept(mTasks.get(i));
- }
- }
-
- private void handleTask(Predicate<Task> callback) {
- for (int i = mTasks.size() - 1; i >= 0; i--) {
- final Task task = mTasks.get(i);
- if (callback.test(task)) {
- return;
- }
- }
- }
-
- private void evaluate(String title, Runnable action) {
- for (int i = 0; i < WARMUP_ITERATIONS; i++) {
- action.run();
- }
- performGc();
-
- final GcStatus startGcStatus = getGcStatus();
- final long startTime = SystemClock.elapsedRealtime();
- for (int i = 0; i < TEST_ITERATIONS; i++) {
- action.run();
- }
- evaluateResult(title, startGcStatus, startTime);
- }
-
- private void evaluateMultiThread(String title, Runnable[] actions) {
- performGc();
-
- final CountDownLatch latch = new CountDownLatch(actions.length);
- final GcStatus startGcStatus = getGcStatus();
- final long startTime = SystemClock.elapsedRealtime();
- for (Runnable action : actions) {
- new Thread() {
- @Override
- public void run() {
- for (int i = 0; i < TEST_ITERATIONS; i++) {
- action.run();
- }
- latch.countDown();
- };
- }.start();
- }
- try {
- latch.await();
- } catch (InterruptedException ignored) {
- }
- evaluateResult(title, startGcStatus, startTime);
- }
-
- private void evaluateResult(String title, GcStatus startStatus, long startTime) {
- final float elapsed = SystemClock.elapsedRealtime() - startTime;
- // Sleep a while to see if GC may happen.
- SystemClock.sleep(DELAY_AFTER_BENCH_MS);
- final GcStatus endStatus = getGcStatus();
- final GcInfo info = startStatus.calculateGcTime(endStatus, title, mTestResults);
- Log.i(TAG, mMethodName + "_" + title + " execution time: "
- + elapsed + "ms (avg=" + String.format("%.5f", elapsed / TEST_ITERATIONS) + "ms)"
- + " GC time: " + String.format("%.3f", info.mTotalGcTime) + "ms"
- + " GC paused time: " + String.format("%.3f", info.mTotalGcPausedTime) + "ms");
- }
-
- /** Cleans the test environment. */
- private static void performGc() {
- System.gc();
- System.runFinalization();
- System.gc();
- }
-
- private static GcStatus getGcStatus() {
- if (DEBUG) {
- Log.i(TAG, "===== Read GC dump =====");
- }
- final GcStatus status = new GcStatus();
- final List<String> vmDump = getVmDump();
- Assume.assumeFalse("VM dump is empty", vmDump.isEmpty());
- for (String line : vmDump) {
- status.visit(line);
- if (line.startsWith("DALVIK THREADS")) {
- break;
- }
- }
- return status;
- }
-
- private static List<String> getVmDump() {
- final int myPid = Process.myPid();
- // Another approach Debug#dumpJavaBacktraceToFileTimeout requires setenforce 0.
- Process.sendSignal(myPid, Process.SIGNAL_QUIT);
- // Give a chance to handle the signal.
- SystemClock.sleep(100);
-
- String dump = null;
- final String pattern = myPid + " written to: ";
- final List<String> logs = shell("logcat -v brief -d tombstoned:I *:S");
- for (int i = logs.size() - 1; i >= 0; i--) {
- final String log = logs.get(i);
- // Log pattern: Traces for pid 9717 written to: /data/anr/trace_07
- final int pos = log.indexOf(pattern);
- if (pos > 0) {
- dump = log.substring(pattern.length() + pos);
- break;
- }
- }
-
- Assume.assumeNotNull("Unable to find VM dump", dump);
- // It requires system or root uid to read the trace.
- return shell("cat " + dump);
- }
-
- private static List<String> shell(String command) {
- final ParcelFileDescriptor.AutoCloseInputStream stream =
- new ParcelFileDescriptor.AutoCloseInputStream(
- getInstrumentation().getUiAutomation().executeShellCommand(command));
- final ArrayList<String> lines = new ArrayList<>();
- try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
- String line;
- while ((line = br.readLine()) != null) {
- lines.add(line);
- }
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- return lines;
- }
-
- /** An empty class which provides some methods with different type arguments. */
- static class Task {
- void doSomething() {
- }
-
- void doSomething(Task t) {
- }
-
- void doSomething(int taskId, long time) {
- }
-
- boolean doSomething(Rect bounds, boolean top, int taskId) {
- return false;
- }
- }
-
- static class ValPattern {
- static final int TYPE_COUNT = 0;
- static final int TYPE_TIME = 1;
- static final String PATTERN_COUNT = "(\\d+)";
- static final String PATTERN_TIME = "(\\d+\\.?\\d+)(\\w+)";
- final String mRawPattern;
- final Pattern mPattern;
- final int mType;
-
- int mIntValue;
- float mFloatValue;
-
- ValPattern(String p, int type) {
- mRawPattern = p;
- mPattern = Pattern.compile(
- p + (type == TYPE_TIME ? PATTERN_TIME : PATTERN_COUNT) + ".*");
- mType = type;
- }
-
- boolean visit(String line) {
- final Matcher matcher = mPattern.matcher(line);
- if (!matcher.matches()) {
- return false;
- }
- final String value = matcher.group(1);
- if (value == null) {
- return false;
- }
- if (mType == TYPE_COUNT) {
- mIntValue = Integer.parseInt(value);
- return true;
- }
- final float time = Float.parseFloat(value);
- final String unit = matcher.group(2);
- if (unit == null) {
- return false;
- }
- // Refer to art/libartbase/base/time_utils.cc
- switch (unit) {
- case "s":
- mFloatValue = time * 1000;
- break;
- case "ms":
- mFloatValue = time;
- break;
- case "us":
- mFloatValue = time / 1000;
- break;
- case "ns":
- mFloatValue = time / 1000 / 1000;
- break;
- default:
- throw new IllegalArgumentException();
- }
-
- return true;
- }
-
- @Override
- public String toString() {
- return mRawPattern + (mType == TYPE_TIME ? (mFloatValue + "ms") : mIntValue);
- }
- }
-
- /** Parses the dump pattern of Heap::DumpGcPerformanceInfo. */
- private static class GcStatus {
- private static final int TOTAL_GC_TIME_INDEX = 1;
- private static final int TOTAL_GC_PAUSED_TIME_INDEX = 5;
-
- // Refer to art/runtime/gc/heap.cc
- final ValPattern[] mPatterns = {
- new ValPattern("Total GC count: ", ValPattern.TYPE_COUNT),
- new ValPattern("Total GC time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total time waiting for GC to complete: ", ValPattern.TYPE_TIME),
- new ValPattern("Total blocking GC count: ", ValPattern.TYPE_COUNT),
- new ValPattern("Total blocking GC time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total mutator paused time: ", ValPattern.TYPE_TIME),
- new ValPattern("Total number of allocations ", ValPattern.TYPE_COUNT),
- new ValPattern("concurrent copying paused: Sum: ", ValPattern.TYPE_TIME),
- new ValPattern("concurrent copying total time: ", ValPattern.TYPE_TIME),
- new ValPattern("concurrent copying freed: ", ValPattern.TYPE_COUNT),
- new ValPattern("Peak regions allocated ", ValPattern.TYPE_COUNT),
- };
-
- void visit(String dumpLine) {
- for (ValPattern p : mPatterns) {
- if (p.visit(dumpLine)) {
- if (DEBUG) {
- Log.i(TAG, " " + p);
- }
- }
- }
- }
-
- GcInfo calculateGcTime(GcStatus newStatus, String title, Bundle result) {
- Log.i(TAG, "===== GC status of " + title + " =====");
- final GcInfo info = new GcInfo();
- for (int i = 0; i < mPatterns.length; i++) {
- final ValPattern p = mPatterns[i];
- if (p.mType == ValPattern.TYPE_COUNT) {
- final int diff = newStatus.mPatterns[i].mIntValue - p.mIntValue;
- Log.i(TAG, " " + p.mRawPattern + diff);
- if (diff > 0) {
- result.putInt("[" + title + "] " + p.mRawPattern, diff);
- }
- continue;
- }
- final float diff = newStatus.mPatterns[i].mFloatValue - p.mFloatValue;
- Log.i(TAG, " " + p.mRawPattern + diff + "ms");
- if (diff > 0) {
- result.putFloat("[" + title + "] " + p.mRawPattern + "(ms)", diff);
- }
- if (i == TOTAL_GC_TIME_INDEX) {
- info.mTotalGcTime = diff;
- } else if (i == TOTAL_GC_PAUSED_TIME_INDEX) {
- info.mTotalGcPausedTime = diff;
- }
- }
- return info;
- }
- }
-
- private static class GcInfo {
- float mTotalGcTime;
- float mTotalGcPausedTime;
- }
-}
+/*
+ * 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.internal;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+
+import com.android.internal.util.function.pooled.PooledConsumer;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.internal.util.function.pooled.PooledPredicate;
+
+import org.junit.Assume;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runners.model.Statement;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Compares the performance of regular lambda and pooled lambda. */
+@LargeTest
+public class LambdaPerfTest {
+ private static final boolean DEBUG = false;
+ private static final String TAG = LambdaPerfTest.class.getSimpleName();
+
+ private static final String LAMBDA_FORM_REGULAR = "regular";
+ private static final String LAMBDA_FORM_POOLED = "pooled";
+
+ private static final int WARMUP_ITERATIONS = 1000;
+ private static final int TEST_ITERATIONS = 3000000;
+ private static final int TASK_COUNT = 10;
+ private static final long DELAY_AFTER_BENCH_MS = 1000;
+
+ private String mMethodName;
+
+ private final Bundle mTestResults = new Bundle();
+ private final ArrayList<Task> mTasks = new ArrayList<>();
+
+ // The member fields are used to ensure lambda capturing. They don't have the actual meaning.
+ private final Task mTask = new Task();
+ private final Rect mBounds = new Rect();
+ private int mTaskId;
+ private long mTime;
+ private boolean mTop;
+
+ @Rule
+ public final TestRule mRule = (base, description) -> new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ mMethodName = description.getMethodName();
+ mTasks.clear();
+ for (int i = 0; i < TASK_COUNT; i++) {
+ final Task t = new Task();
+ mTasks.add(t);
+ }
+ base.evaluate();
+
+ getInstrumentation().sendStatus(Activity.RESULT_OK, mTestResults);
+ }
+ };
+
+ @Test
+ public void test1ParamConsumer() {
+ evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTask)));
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+ PooledLambda.__(Task.class), mTask);
+ forAllTask(c);
+ c.recycle();
+ });
+ }
+
+ @Test
+ public void test2PrimitiveParamsConsumer() {
+ // Not in Integer#IntegerCache (-128~127) for autoboxing, that may create new object.
+ mTaskId = 12345;
+ mTime = 54321;
+
+ evaluate(LAMBDA_FORM_REGULAR, () -> forAllTask(t -> t.doSomething(mTaskId, mTime)));
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+ PooledLambda.__(Task.class), mTaskId, mTime);
+ forAllTask(c);
+ c.recycle();
+ });
+ }
+
+ @Test
+ public void test3ParamsPredicate() {
+ mTop = true;
+ // In Integer#IntegerCache.
+ mTaskId = 10;
+
+ evaluate(LAMBDA_FORM_REGULAR, () -> handleTask(t -> t.doSomething(mBounds, mTop, mTaskId)));
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final PooledPredicate c = PooledLambda.obtainPredicate(Task::doSomething,
+ PooledLambda.__(Task.class), mBounds, mTop, mTaskId);
+ handleTask(c);
+ c.recycle();
+ });
+ }
+
+ @Test
+ public void testMessage() {
+ evaluate(LAMBDA_FORM_REGULAR, () -> {
+ final Message m = Message.obtain().setCallback(() -> mTask.doSomething(mTaskId, mTime));
+ m.getCallback().run();
+ m.recycle();
+ });
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final Message m = PooledLambda.obtainMessage(Task::doSomething, mTask, mTaskId, mTime);
+ m.getCallback().run();
+ m.recycle();
+ });
+ }
+
+ @Test
+ public void testRunnable() {
+ evaluate(LAMBDA_FORM_REGULAR, () -> {
+ final Runnable r = mTask::doSomething;
+ r.run();
+ });
+ evaluate(LAMBDA_FORM_POOLED, () -> {
+ final Runnable r = PooledLambda.obtainRunnable(Task::doSomething, mTask).recycleOnUse();
+ r.run();
+ });
+ }
+
+ @Test
+ public void testMultiThread() {
+ final int numThread = 3;
+
+ final Runnable regularAction = () -> forAllTask(t -> t.doSomething(mTask));
+ final Runnable[] regularActions = new Runnable[numThread];
+ Arrays.fill(regularActions, regularAction);
+ evaluateMultiThread(LAMBDA_FORM_REGULAR, regularActions);
+
+ final Runnable pooledAction = () -> {
+ final PooledConsumer c = PooledLambda.obtainConsumer(Task::doSomething,
+ PooledLambda.__(Task.class), mTask);
+ forAllTask(c);
+ c.recycle();
+ };
+ final Runnable[] pooledActions = new Runnable[numThread];
+ Arrays.fill(pooledActions, pooledAction);
+ evaluateMultiThread(LAMBDA_FORM_POOLED, pooledActions);
+ }
+
+ private void forAllTask(Consumer<Task> callback) {
+ for (int i = mTasks.size() - 1; i >= 0; i--) {
+ callback.accept(mTasks.get(i));
+ }
+ }
+
+ private void handleTask(Predicate<Task> callback) {
+ for (int i = mTasks.size() - 1; i >= 0; i--) {
+ final Task task = mTasks.get(i);
+ if (callback.test(task)) {
+ return;
+ }
+ }
+ }
+
+ private void evaluate(String title, Runnable action) {
+ for (int i = 0; i < WARMUP_ITERATIONS; i++) {
+ action.run();
+ }
+ performGc();
+
+ final GcStatus startGcStatus = getGcStatus();
+ final long startTime = SystemClock.elapsedRealtime();
+ for (int i = 0; i < TEST_ITERATIONS; i++) {
+ action.run();
+ }
+ evaluateResult(title, startGcStatus, startTime);
+ }
+
+ private void evaluateMultiThread(String title, Runnable[] actions) {
+ performGc();
+
+ final CountDownLatch latch = new CountDownLatch(actions.length);
+ final GcStatus startGcStatus = getGcStatus();
+ final long startTime = SystemClock.elapsedRealtime();
+ for (Runnable action : actions) {
+ new Thread() {
+ @Override
+ public void run() {
+ for (int i = 0; i < TEST_ITERATIONS; i++) {
+ action.run();
+ }
+ latch.countDown();
+ };
+ }.start();
+ }
+ try {
+ latch.await();
+ } catch (InterruptedException ignored) {
+ }
+ evaluateResult(title, startGcStatus, startTime);
+ }
+
+ private void evaluateResult(String title, GcStatus startStatus, long startTime) {
+ final float elapsed = SystemClock.elapsedRealtime() - startTime;
+ // Sleep a while to see if GC may happen.
+ SystemClock.sleep(DELAY_AFTER_BENCH_MS);
+ final GcStatus endStatus = getGcStatus();
+ final GcInfo info = startStatus.calculateGcTime(endStatus, title, mTestResults);
+ mTestResults.putFloat("[" + title + "-execution-time]", elapsed);
+ Log.i(TAG, mMethodName + "_" + title + " execution time: "
+ + elapsed + "ms (avg=" + String.format("%.5f", elapsed / TEST_ITERATIONS) + "ms)"
+ + " GC time: " + String.format("%.3f", info.mTotalGcTime) + "ms"
+ + " GC paused time: " + String.format("%.3f", info.mTotalGcPausedTime) + "ms");
+ }
+
+ /** Cleans the test environment. */
+ private static void performGc() {
+ System.gc();
+ System.runFinalization();
+ System.gc();
+ }
+
+ private static GcStatus getGcStatus() {
+ if (DEBUG) {
+ Log.i(TAG, "===== Read GC dump =====");
+ }
+ final GcStatus status = new GcStatus();
+ final List<String> vmDump = getVmDump();
+ Assume.assumeFalse("VM dump is empty", vmDump.isEmpty());
+ for (String line : vmDump) {
+ status.visit(line);
+ if (line.startsWith("DALVIK THREADS")) {
+ break;
+ }
+ }
+ return status;
+ }
+
+ private static List<String> getVmDump() {
+ final int myPid = Process.myPid();
+ // Another approach Debug#dumpJavaBacktraceToFileTimeout requires setenforce 0.
+ Process.sendSignal(myPid, Process.SIGNAL_QUIT);
+ // Give a chance to handle the signal.
+ SystemClock.sleep(100);
+
+ String dump = null;
+ final String pattern = myPid + " written to: ";
+ final List<String> logs = shell("logcat -v brief -d tombstoned:I *:S");
+ for (int i = logs.size() - 1; i >= 0; i--) {
+ final String log = logs.get(i);
+ // Log pattern: Traces for pid 9717 written to: /data/anr/trace_07
+ final int pos = log.indexOf(pattern);
+ if (pos > 0) {
+ dump = log.substring(pattern.length() + pos);
+ if (!dump.startsWith("/data/anr/")) {
+ dump = "/data/anr/" + dump;
+ }
+ break;
+ }
+ }
+
+ Assume.assumeNotNull("Unable to find VM dump", dump);
+ // It requires system or root uid to read the trace.
+ return shell("cat " + dump);
+ }
+
+ private static List<String> shell(String command) {
+ final ParcelFileDescriptor.AutoCloseInputStream stream =
+ new ParcelFileDescriptor.AutoCloseInputStream(
+ getInstrumentation().getUiAutomation().executeShellCommand(command));
+ final ArrayList<String> lines = new ArrayList<>();
+ try (BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ lines.add(line);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return lines;
+ }
+
+ /** An empty class which provides some methods with different type arguments. */
+ static class Task {
+ void doSomething() {
+ }
+
+ void doSomething(Task t) {
+ }
+
+ void doSomething(int taskId, long time) {
+ }
+
+ boolean doSomething(Rect bounds, boolean top, int taskId) {
+ return false;
+ }
+ }
+
+ static class ValPattern {
+ static final int TYPE_COUNT = 0;
+ static final int TYPE_TIME = 1;
+ static final String PATTERN_COUNT = "(\\d+)";
+ static final String PATTERN_TIME = "(\\d+\\.?\\d+)(\\w+)";
+ final String mRawPattern;
+ final Pattern mPattern;
+ final int mType;
+
+ int mIntValue;
+ float mFloatValue;
+
+ ValPattern(String p, int type) {
+ mRawPattern = p;
+ mPattern = Pattern.compile(
+ p + (type == TYPE_TIME ? PATTERN_TIME : PATTERN_COUNT) + ".*");
+ mType = type;
+ }
+
+ boolean visit(String line) {
+ final Matcher matcher = mPattern.matcher(line);
+ if (!matcher.matches()) {
+ return false;
+ }
+ final String value = matcher.group(1);
+ if (value == null) {
+ return false;
+ }
+ if (mType == TYPE_COUNT) {
+ mIntValue = Integer.parseInt(value);
+ return true;
+ }
+ final float time = Float.parseFloat(value);
+ final String unit = matcher.group(2);
+ if (unit == null) {
+ return false;
+ }
+ // Refer to art/libartbase/base/time_utils.cc
+ switch (unit) {
+ case "s":
+ mFloatValue = time * 1000;
+ break;
+ case "ms":
+ mFloatValue = time;
+ break;
+ case "us":
+ mFloatValue = time / 1000;
+ break;
+ case "ns":
+ mFloatValue = time / 1000 / 1000;
+ break;
+ default:
+ throw new IllegalArgumentException();
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return mRawPattern + (mType == TYPE_TIME ? (mFloatValue + "ms") : mIntValue);
+ }
+ }
+
+ /** Parses the dump pattern of Heap::DumpGcPerformanceInfo. */
+ private static class GcStatus {
+ private static final int TOTAL_GC_TIME_INDEX = 1;
+ private static final int TOTAL_GC_PAUSED_TIME_INDEX = 5;
+
+ // Refer to art/runtime/gc/heap.cc
+ final ValPattern[] mPatterns = {
+ new ValPattern("Total GC count: ", ValPattern.TYPE_COUNT),
+ new ValPattern("Total GC time: ", ValPattern.TYPE_TIME),
+ new ValPattern("Total time waiting for GC to complete: ", ValPattern.TYPE_TIME),
+ new ValPattern("Total blocking GC count: ", ValPattern.TYPE_COUNT),
+ new ValPattern("Total blocking GC time: ", ValPattern.TYPE_TIME),
+ new ValPattern("Total mutator paused time: ", ValPattern.TYPE_TIME),
+ new ValPattern("Total number of allocations ", ValPattern.TYPE_COUNT),
+ new ValPattern("concurrent copying paused: Sum: ", ValPattern.TYPE_TIME),
+ new ValPattern("concurrent copying total time: ", ValPattern.TYPE_TIME),
+ new ValPattern("concurrent copying freed: ", ValPattern.TYPE_COUNT),
+ new ValPattern("Peak regions allocated ", ValPattern.TYPE_COUNT),
+ };
+
+ void visit(String dumpLine) {
+ for (ValPattern p : mPatterns) {
+ if (p.visit(dumpLine)) {
+ if (DEBUG) {
+ Log.i(TAG, " " + p);
+ }
+ }
+ }
+ }
+
+ GcInfo calculateGcTime(GcStatus newStatus, String title, Bundle result) {
+ Log.i(TAG, "===== GC status of " + title + " =====");
+ final GcInfo info = new GcInfo();
+ for (int i = 0; i < mPatterns.length; i++) {
+ final ValPattern p = mPatterns[i];
+ if (p.mType == ValPattern.TYPE_COUNT) {
+ final int diff = newStatus.mPatterns[i].mIntValue - p.mIntValue;
+ Log.i(TAG, " " + p.mRawPattern + diff);
+ if (diff > 0) {
+ result.putInt("[" + title + "] " + p.mRawPattern, diff);
+ }
+ continue;
+ }
+ final float diff = newStatus.mPatterns[i].mFloatValue - p.mFloatValue;
+ Log.i(TAG, " " + p.mRawPattern + diff + "ms");
+ if (diff > 0) {
+ result.putFloat("[" + title + "] " + p.mRawPattern + "(ms)", diff);
+ }
+ if (i == TOTAL_GC_TIME_INDEX) {
+ info.mTotalGcTime = diff;
+ } else if (i == TOTAL_GC_PAUSED_TIME_INDEX) {
+ info.mTotalGcPausedTime = diff;
+ }
+ }
+ return info;
+ }
+ }
+
+ private static class GcInfo {
+ float mTotalGcTime;
+ float mTotalGcPausedTime;
+ }
+}
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 7efe3c3472fa..7ddbe95aa79b 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -130,7 +130,7 @@ cc_library_host_static {
"optimize/MultiApkGenerator.cpp",
"optimize/ResourceDeduper.cpp",
"optimize/ResourceFilter.cpp",
- "optimize/ResourcePathShortener.cpp",
+ "optimize/Obfuscator.cpp",
"optimize/VersionCollapser.cpp",
"process/SymbolTable.cpp",
"split/TableSplitter.cpp",
@@ -161,6 +161,7 @@ cc_library_host_static {
"ApkInfo.proto",
"Configuration.proto",
"Resources.proto",
+ "ResourceMetadata.proto",
"ResourcesInternal.proto",
"ValueTransformer.cpp",
],
@@ -218,6 +219,7 @@ genrule {
srcs: [
"Configuration.proto",
"ResourcesInternal.proto",
+ "ResourceMetadata.proto",
"Resources.proto",
],
out: ["aapt2-protos.zip"],
diff --git a/tools/aapt2/ResourceMetadata.proto b/tools/aapt2/ResourceMetadata.proto
new file mode 100644
index 000000000000..8eca54c4da5e
--- /dev/null
+++ b/tools/aapt2/ResourceMetadata.proto
@@ -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.
+ */
+
+syntax = "proto3";
+
+package aapt.pb;
+
+option java_package = "com.android.aapt";
+option java_multiple_files = true;
+
+message ResourceMappings {
+ ShortenedPathsMap shortened_paths = 1;
+ CollapsedNamesMap collapsed_names = 2;
+}
+
+// Metadata relating to "aapt2 optimize --shorten-resource-paths"
+message ShortenedPathsMap {
+ // Maps shorted paths (e.g. "res/foo.xml") to their original names (e.g.
+ // "res/xml/file_with_long_name.xml").
+ message ResourcePathMapping {
+ string shortened_path = 1;
+ string original_path = 2;
+ }
+ repeated ResourcePathMapping resource_paths = 1;
+}
+
+// Metadata relating to "aapt2 optimize --collapse-resource-names"
+message CollapsedNamesMap {
+ // Maps resource IDs (e.g. 0x7f123456) to their original names (e.g.
+ // "package:type/entry").
+ message ResourceNameMapping {
+ uint32 id = 1;
+ string name = 2;
+ }
+ repeated ResourceNameMapping resource_names = 1;
+}
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index e37c2d450b73..9feaf524eaf1 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -16,7 +16,11 @@
#include "Optimize.h"
+#include <map>
#include <memory>
+#include <set>
+#include <string>
+#include <utility>
#include <vector>
#include "Diagnostics.h"
@@ -38,9 +42,9 @@
#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "optimize/MultiApkGenerator.h"
+#include "optimize/Obfuscator.h"
#include "optimize/ResourceDeduper.h"
#include "optimize/ResourceFilter.h"
-#include "optimize/ResourcePathShortener.h"
#include "optimize/VersionCollapser.h"
#include "split/TableSplitter.h"
#include "util/Files.h"
@@ -114,11 +118,11 @@ class OptimizeContext : public IAaptContext {
}
private:
- DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
-
StdErrDiagnostics diagnostics_;
bool verbose_ = false;
int sdk_version_ = 0;
+
+ DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
};
class Optimizer {
@@ -151,8 +155,8 @@ class Optimizer {
}
if (options_.shorten_resource_paths) {
- ResourcePathShortener shortener(options_.table_flattener_options.shortened_path_map);
- if (!shortener.Consume(context_, apk->GetResourceTable())) {
+ Obfuscator obfuscator(options_.table_flattener_options.shortened_path_map);
+ if (!obfuscator.Consume(context_, apk->GetResourceTable())) {
context_->GetDiagnostics()->Error(android::DiagMessage()
<< "failed shortening resource paths");
return 1;
diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/Obfuscator.cpp
index 7ff9bf5aa8df..f704f26bfd29 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.cpp
+++ b/tools/aapt2/optimize/Obfuscator.cpp
@@ -14,28 +14,25 @@
* limitations under the License.
*/
-#include "optimize/ResourcePathShortener.h"
+#include "optimize/Obfuscator.h"
#include <set>
+#include <string>
#include <unordered_set>
-#include "androidfw/StringPiece.h"
-
#include "ResourceTable.h"
#include "ValueVisitor.h"
+#include "androidfw/StringPiece.h"
#include "util/Util.h"
-
-static const std::string base64_chars =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz"
- "0123456789-_";
+static const char base64_chars[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "0123456789-_";
namespace aapt {
-ResourcePathShortener::ResourcePathShortener(
- std::map<std::string, std::string>& path_map_out)
- : path_map_(path_map_out) {
+Obfuscator::Obfuscator(std::map<std::string, std::string>& path_map_out) : path_map_(path_map_out) {
}
std::string ShortenFileName(const android::StringPiece& file_path, int output_length) {
@@ -50,7 +47,6 @@ std::string ShortenFileName(const android::StringPiece& file_path, int output_le
return result;
}
-
// Return the optimal hash length such that at most 10% of resources collide in
// their shortened path.
// Reference: http://matt.might.net/articles/counting-hash-collisions/
@@ -63,7 +59,7 @@ int OptimalShortenedLength(int num_resources) {
}
std::string GetShortenedPath(const android::StringPiece& shortened_filename,
- const android::StringPiece& extension, int collision_count) {
+ const android::StringPiece& extension, int collision_count) {
std::string shortened_path = "res/" + shortened_filename.to_string();
if (collision_count > 0) {
shortened_path += std::to_string(collision_count);
@@ -76,12 +72,12 @@ std::string GetShortenedPath(const android::StringPiece& shortened_filename,
// underlying filepath as key rather than the integer address. This is to ensure
// determinism of output for colliding files.
struct PathComparator {
- bool operator() (const FileReference* lhs, const FileReference* rhs) const {
- return lhs->path->compare(*rhs->path);
- }
+ bool operator()(const FileReference* lhs, const FileReference* rhs) const {
+ return lhs->path->compare(*rhs->path);
+ }
};
-bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) {
+bool Obfuscator::Consume(IAaptContext* context, ResourceTable* table) {
// used to detect collisions
std::unordered_set<std::string> shortened_paths;
std::set<FileReference*, PathComparator> file_refs;
@@ -103,8 +99,7 @@ bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table)
util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension);
// Android detects ColorStateLists via pathname, skip res/color*
- if (util::StartsWith(res_subdir, "res/color"))
- continue;
+ if (util::StartsWith(res_subdir, "res/color")) continue;
std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars);
int collision_count = 0;
diff --git a/tools/aapt2/optimize/ResourcePathShortener.h b/tools/aapt2/optimize/Obfuscator.h
index f1074ef083bd..1ea32db12815 100644
--- a/tools/aapt2/optimize/ResourcePathShortener.h
+++ b/tools/aapt2/optimize/Obfuscator.h
@@ -14,13 +14,13 @@
* limitations under the License.
*/
-#ifndef AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
-#define AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
+#ifndef TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
+#define TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
#include <map>
+#include <string>
#include "android-base/macros.h"
-
#include "process/IResourceTableConsumer.h"
namespace aapt {
@@ -28,17 +28,17 @@ namespace aapt {
class ResourceTable;
// Maps resources in the apk to shortened paths.
-class ResourcePathShortener : public IResourceTableConsumer {
+class Obfuscator : public IResourceTableConsumer {
public:
- explicit ResourcePathShortener(std::map<std::string, std::string>& path_map_out);
+ explicit Obfuscator(std::map<std::string, std::string>& path_map_out);
bool Consume(IAaptContext* context, ResourceTable* table) override;
private:
- DISALLOW_COPY_AND_ASSIGN(ResourcePathShortener);
std::map<std::string, std::string>& path_map_;
+ DISALLOW_COPY_AND_ASSIGN(Obfuscator);
};
-} // namespace aapt
+} // namespace aapt
-#endif // AAPT_OPTIMIZE_RESOURCEPATHSHORTENER_H
+#endif // TOOLS_AAPT2_OPTIMIZE_OBFUSCATOR_H_
diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/Obfuscator_test.cpp
index f5a02be0ea5e..a3339d486d4a 100644
--- a/tools/aapt2/optimize/ResourcePathShortener_test.cpp
+++ b/tools/aapt2/optimize/Obfuscator_test.cpp
@@ -14,15 +14,18 @@
* limitations under the License.
*/
-#include "optimize/ResourcePathShortener.h"
+#include "optimize/Obfuscator.h"
+
+#include <memory>
+#include <string>
#include "ResourceTable.h"
#include "test/Test.h"
using ::aapt::test::GetValue;
+using ::testing::Eq;
using ::testing::Not;
using ::testing::NotNull;
-using ::testing::Eq;
android::StringPiece GetExtension(android::StringPiece path) {
auto iter = std::find(path.begin(), path.end(), '.');
@@ -30,16 +33,15 @@ android::StringPiece GetExtension(android::StringPiece path) {
}
void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) {
- for (int i=start; i<end; i++) {
- builder.AddFileReference(
- "android:drawable/xmlfile" + std::to_string(i),
- "res/drawable/xmlfile" + std::to_string(i) + ".xml");
+ for (int i = start; i < end; i++) {
+ builder.AddFileReference("android:drawable/xmlfile" + std::to_string(i),
+ "res/drawable/xmlfile" + std::to_string(i) + ".xml");
}
}
namespace aapt {
-TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
+TEST(ObfuscatorTest, FileRefPathsChangedInResourceTable) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
@@ -50,7 +52,7 @@ TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
.Build();
std::map<std::string, std::string> path_map;
- ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+ ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawables/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -64,39 +66,36 @@ TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) {
EXPECT_THAT(path_map["res/drawables/xmlfile.xml"],
Not(Eq(path_map["res/drawables/xmlfile2.xml"])));
- FileReference* ref =
- GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
+ FileReference* ref = GetValue<FileReference>(table.get(), "android:drawable/xmlfile");
ASSERT_THAT(ref, NotNull());
// The map correctly points to the new location of the file
EXPECT_THAT(path_map["res/drawables/xmlfile.xml"], Eq(*ref->path));
// Strings should not be affected, only file paths
- EXPECT_THAT(
- *GetValue<String>(table.get(), "android:string/string")->value,
+ EXPECT_THAT(*GetValue<String>(table.get(), "android:string/string")->value,
Eq("res/should/still/be/the/same.png"));
EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end()));
}
-TEST(ResourcePathShortenerTest, SkipColorFileRefPaths) {
+TEST(ObfuscatorTest, SkipColorFileRefPaths) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table =
test::ResourceTableBuilder()
.AddFileReference("android:color/colorlist", "res/color/colorlist.xml")
- .AddFileReference("android:color/colorlist",
- "res/color-mdp-v21/colorlist.xml",
+ .AddFileReference("android:color/colorlist", "res/color-mdp-v21/colorlist.xml",
test::ParseConfigOrDie("mdp-v21"))
.Build();
std::map<std::string, std::string> path_map;
- ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+ ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
// Expect that the path map to not contain the ColorStateList
ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end()));
ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end()));
}
-TEST(ResourcePathShortenerTest, KeepExtensions) {
+TEST(ObfuscatorTest, KeepExtensions) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::string original_xml_path = "res/drawable/xmlfile.xml";
@@ -109,7 +108,7 @@ TEST(ResourcePathShortenerTest, KeepExtensions) {
.Build();
std::map<std::string, std::string> path_map;
- ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get()));
+ ASSERT_TRUE(Obfuscator(path_map).Consume(context.get(), table.get()));
// Expect that the path map is populated
ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end())));
@@ -122,7 +121,7 @@ TEST(ResourcePathShortenerTest, KeepExtensions) {
EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png")));
}
-TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) {
+TEST(ObfuscatorTest, DeterministicallyHandleCollisions) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
// 4000 resources is the limit at which the hash space is expanded to 3
@@ -135,27 +134,27 @@ TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) {
FillTable(builder1, 0, kNumResources);
std::unique_ptr<ResourceTable> table1 = builder1.Build();
std::map<std::string, std::string> expected_mapping;
- ASSERT_TRUE(ResourcePathShortener(expected_mapping).Consume(context.get(), table1.get()));
+ ASSERT_TRUE(Obfuscator(expected_mapping).Consume(context.get(), table1.get()));
// We are trying to ensure lack of non-determinism, it is not simple to prove
// a negative, thus we must try the test a few times so that the test itself
// is non-flaky. Basically create the pathmap 5 times from the same set of
// resources but a different order of addition and then ensure they are always
// mapped to the same short path.
- for (int i=0; i<kNumTries; i++) {
+ for (int i = 0; i < kNumTries; i++) {
test::ResourceTableBuilder builder2;
// This loop adds resources to the resource table in the range of
// [0:kNumResources). Adding the file references in different order makes
// non-determinism more likely to surface. Thus we add resources
// [start_index:kNumResources) first then [0:start_index). We also use a
// different start_index each run.
- int start_index = (kNumResources/kNumTries)*i;
+ int start_index = (kNumResources / kNumTries) * i;
FillTable(builder2, start_index, kNumResources);
FillTable(builder2, 0, start_index);
std::unique_ptr<ResourceTable> table2 = builder2.Build();
std::map<std::string, std::string> actual_mapping;
- ASSERT_TRUE(ResourcePathShortener(actual_mapping).Consume(context.get(), table2.get()));
+ ASSERT_TRUE(Obfuscator(actual_mapping).Consume(context.get(), table2.get()));
for (auto& item : actual_mapping) {
ASSERT_THAT(expected_mapping[item.first], Eq(item.second));
@@ -163,4 +162,4 @@ TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) {
}
}
-} // namespace aapt
+} // namespace aapt
diff --git a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
index a415217105a8..bba819cd9096 100644
--- a/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
+++ b/tools/lint/checks/src/main/java/com/google/android/lint/aidl/EnforcePermissionDetector.kt
@@ -82,8 +82,16 @@ class EnforcePermissionDetector : Detector(), SourceCodeScanner {
if (attr1[i].name != attr2[i].name) {
return false
}
- val v1 = ConstantEvaluator.evaluate(context, attr1[i].value)
- val v2 = ConstantEvaluator.evaluate(context, attr2[i].value)
+ val value1 = attr1[i].value
+ val value2 = attr2[i].value
+ if (value1 == null && value2 == null) {
+ continue
+ }
+ if (value1 == null || value2 == null) {
+ return false
+ }
+ val v1 = ConstantEvaluator.evaluate(context, value1)
+ val v2 = ConstantEvaluator.evaluate(context, value2)
if (v1 != v2) {
return false
}
diff --git a/tools/processors/staledataclass/Android.bp b/tools/processors/staledataclass/Android.bp
index 1e5097662a0a..2169c49a91a5 100644
--- a/tools/processors/staledataclass/Android.bp
+++ b/tools/processors/staledataclass/Android.bp
@@ -22,17 +22,13 @@ java_plugin {
static_libs: [
"codegen-version-info",
],
- // The --add-modules/exports flags below don't work for kotlinc yet, so pin this module to Java language level 8 (see b/139342589):
- java_version: "1.8",
- openjdk9: {
- javacflags: [
- "--add-modules=jdk.compiler",
- "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
- "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
- "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
- "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
- ],
- },
+ javacflags: [
+ "--add-modules=jdk.compiler",
+ "--add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED",
+ "--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED",
+ "--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
+ "--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED",
+ ],
use_tools_jar: true,
}
diff --git a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
index 27a8853a2219..1cef5b0c8dfb 100644
--- a/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
+++ b/tools/processors/staledataclass/src/android/processor/staledataclass/StaleDataclassProcessor.kt
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+@file:Suppress("JAVA_MODULE_DOES_NOT_EXPORT_PACKAGE")
package android.processor.staledataclass